<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" >
  <link rel="dns-prefetch" href="http://angegit.gitee.io">
  <title>Docker | Nie Changan</title>
  <meta name="generator" content="hexo-theme-yilia-plus">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  
  <meta name="description" content="Docker 作者:聂长安">
<meta property="og:type" content="article">
<meta property="og:title" content="Docker">
<meta property="og:url" content="http://angegit.gitee.io/2020/03/30/Docker/index.html">
<meta property="og:site_name" content="Nie Changan">
<meta property="og:description" content="Docker 作者:聂长安">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/virtualization.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/docker.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/620140640_31678.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/262150629_86976.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/1107037-20180128094640209-1433322312.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/1107037-20180128095038600-772177322.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/1107037-20180128103448287-493824081.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/1107037-20180128104046600-1053107877.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/1107037-20180128103145287-536100760.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/images-mac-example-nginx.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/images-create-nginx-docker.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/swarm-diagram.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/services-diagram.png">
<meta property="og:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/machine.png">
<meta property="article:published_time" content="2020-03-30T03:00:30.000Z">
<meta property="article:modified_time" content="2020-03-30T05:52:05.080Z">
<meta property="article:author" content="Nie Changan">
<meta property="article:tag" content="Java">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://angegit.gitee.io/myblog/2020/03/30/Docker/virtualization.png">
  
    <link rel="alternative" href="/myblog/atom.xml" title="Nie Changan" type="application/atom+xml">
  
  
    <link rel="icon" href="/myblog/favicon.ico">
  
  
    <link rel="apple-touch-icon" href="/myblog/apple-touch-icon-180x180.png">
  
  <link rel="stylesheet" type="text/css" href="/myblog/./main.68f21c.css">
  <style type="text/css">
    
    #container.show {
      background: linear-gradient(200deg,#a0cfe4,#e8c37e);
    }
  </style>
  

  

  
    
 	  <script src="/myblog/lib/clickLove.js"></script>
  
  


  

</head>

<body>
  <div id="container" q-class="show:isCtnShow">
    <canvas id="anm-canvas" class="anm-canvas"></canvas>
    <div class="left-col" q-class="show:isShow">
      


<div class="overlay" style="background: #4d4d4d;background: url('/myblog/img/biubiubiu.gif') no-repeat ;"></div>
<div class="intrude-less">
	<header id="header" class="inner">
		<a href="/myblog/" class="profilepic">
			<img src="/myblog/img/head.jpg" class="js-avatar">
		</a>
		<hgroup>
			<h1 class="header-author"><a href="/myblog/">Nie Changan</a></h1>
		</hgroup>
		
		<p class="header-subtitle">生如蝼蚁,当有鸿鹄之志</p>
		

		<nav class="header-menu">
			<ul>
			
				<li><a href="/myblog/" target="_blank">主页</a></li>
			
				<li><a href="http://angegit.gitee.io/cv/" target="_blank">个人简历</a></li>
			
				<li><a href="/myblog/tags/Linux/" target="_blank">Linux</a></li>
			
				<li><a href="/myblog/tags/Java/" target="_blank">Java</a></li>
			
			</ul>
		</nav>
		<nav class="header-smart-menu">
			
				
					<a q-on="click: openSlider(e, 'innerArchive')" href="javascript:void(0)">所有文章</a>
				
			
				
					<a q-on="click: openSlider(e, 'friends')" href="javascript:void(0)">友链</a>
				
			
				
					<a q-on="click: openSlider(e, 'aboutme')" href="javascript:void(0)">关于我</a>
				
			
		</nav>
		<nav class="header-nav">
			<div class="social">
				
					<a class="gitee" href="https://gitee.com/AngeGit" title="gitee" target="_blank"><i class="icon-gitee"></i></a>
				
					<a class="csdn" href="https://blog.csdn.net/ca1993422" title="CSDN" target="_blank"><i class="icon-csdn"></i></a>
				
					<a class="weixin" href="/myblog/img/wechat.jpg" title="wechat" target="_blank"><i class="icon-weixin"></i></a>
				
					<a class="bilibili" href="https://space.bilibili.com/380963393" title="bilibili" target="_blank"><i class="icon-bilibili"></i></a>
				
			</div>
		
			
			
			
			
			<div>
				<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width="240" height="86" src="//music.163.com/outchain/player?type=2&id=1334445174&auto=1&height=66"></iframe>
			</div>
			
				
				<p style="font-size: 12px;">这似乎是首纯音乐，请尽情的欣赏它吧！<p>
			
		
		</nav>
	</header>		
</div>

    </div>
    <div class="mid-col" q-class="show:isShow,hide:isShow|isFalse">
      
      
      <a class="forkMe" style="position:absolute;z-index:999;top:0;right:0.5em;" 
        href="https://gitee.com/AngeGit" target="_blank">
        <img src="/myblog/img/forkme.png"
          class="attachment-full size-full" alt="Fork me on GitHub" data-recalc-dims="1"></a>
      
      
<nav id="mobile-nav">
  	<div class="overlay js-overlay" style="background: #4d4d4d"></div>
	<div class="btnctn js-mobile-btnctn">
  		<div class="slider-trigger list" q-on="click: openSlider(e)"><i class="icon icon-sort"></i></div>
	</div>
	<div class="intrude-less">
		<header id="header" class="inner">
			<div class="profilepic">
				<a href="/myblog/">
					<img src="/myblog/img/head.jpg" class="js-avatar">
				</a>
			</div>
			<hgroup>
			  <h1 class="header-author js-header-author">Nie Changan</h1>
			</hgroup>
			
			<p class="header-subtitle"><i class="icon icon-quo-left"></i>生如蝼蚁,当有鸿鹄之志<i class="icon icon-quo-right"></i></p>
			
			
			
				
			
				
			
				
			
				
			
			
			
			<nav class="header-nav">
				<div class="social">
					
						<a class="gitee" target="_blank" href="https://gitee.com/AngeGit" title="gitee"><i class="icon-gitee"></i></a>
			        
						<a class="csdn" target="_blank" href="https://blog.csdn.net/ca1993422" title="CSDN"><i class="icon-csdn"></i></a>
			        
						<a class="weixin" target="_blank" href="/myblog/img/wechat.jpg" title="wechat"><i class="icon-weixin"></i></a>
			        
						<a class="bilibili" target="_blank" href="https://space.bilibili.com/380963393" title="bilibili"><i class="icon-bilibili"></i></a>
			        
				</div>
			</nav>

			<nav class="header-menu js-header-menu">
				<ul style="width: 70%">
				
				
					<li style="width: 25%"><a href="/myblog/">主页</a></li>
		        
					<li style="width: 25%"><a href="http://angegit.gitee.io/cv/">个人简历</a></li>
		        
					<li style="width: 25%"><a href="/myblog/tags/Linux/">Linux</a></li>
		        
					<li style="width: 25%"><a href="/myblog/tags/Java/">Java</a></li>
		        
				</ul>
			</nav>
		</header>				
	</div>
	<div class="mobile-mask" style="display:none" q-show="isShow"></div>
</nav>

      <div id="wrapper" class="body-wrap">
        <div class="menu-l">
          <div class="canvas-wrap">
            <canvas data-colors="#eaeaea" data-sectionHeight="100" data-contentId="js-content" id="myCanvas1"
              class="anm-canvas"></canvas>
          </div>
          <div id="js-content" class="content-ll">
            <article id="post-Docker" class="article article-type-post " itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
  
  
    <h1 class="article-title" itemprop="name">
      Docker
    </h1>
  


  
  
        
        <span id="busuanzi_container_page_pv" style='display:none' class="archive-article-date">
              <i class="icon-smile icon"></i> 阅读数：<span id="busuanzi_value_page_pv"></span>次
        </span>

<a href="/myblog/2020/03/30/Docker/" class="archive-article-date">
        <time datetime="2020-03-30T03:00:30.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2020-03-30</time>
</a>

  
  
    
<div style="margin-top:10px;">
  <span class="post-time">
    <span class="post-meta-item-icon">
      <!-- fonts.scss -->
      <!-- 百度字体平台:http://fontstore.baidu.com/static/editor/index.html -->
      <i class="icon-statistics"></i>
      <span class="post-meta-item-text"> 字数统计:</span>
      <span class="post-count">28.6k字</span>
    </span>
  </span>

  <span class="post-time">
    &nbsp; | &nbsp;
    <span class="post-meta-item-icon">
      <i class="icon-book icon"></i>
      <span class="post-meta-item-text"> 阅读时长≈</span>
      <span class="post-count">111分</span>
    </span>
  </span>
</div>


  
  </header>
  
  <div class="article-entry" itemprop="articleBody">
    
    <h1 id="Docker"><a href="#Docker" class="headerlink" title="Docker"></a>Docker</h1><blockquote>
<p>作者:Nie Changan 转载请标明出处</p>
</blockquote>
<h2 id="Build-once，Run-anywhere"><a href="#Build-once，Run-anywhere" class="headerlink" title="Build once，Run anywhere"></a>Build once，Run anywhere</h2><p>Docker 使用 Google 公司推出的 Go 语言 进行开发实现，基于 Linux 内核的 cgroup，namespace，以及<br>AUFS 类的 Union FS 等技术，对进程进行封装隔离，属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程，因此也称其为容器。最初实现是基于 LXC，从 0.7 版本以后开始去除 LXC，转而使用自行开发的 libcontainer，从 1.11 开始，则进一步演进为使用 runC 和 containerd。</p>
<a id="more"></a>

<h2 id="什么是-Docker"><a href="#什么是-Docker" class="headerlink" title="什么是 Docker"></a>什么是 Docker</h2><p>Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目，它是基于 dotCloud 公司多年云服务技术的一次革新，并于 <a href="https://en.wikipedia.org/wiki/Docker_(software)" target="_blank" rel="noopener">2013 年 3 月以 Apache 2.0 授权协议开源</a><a href="https://en.wikipedia.org/wiki/Docker_(software)" target="_blank" rel="noopener"> </a>，主要项目代码在 <a href="https://github.com/moby/moby" target="_blank" rel="noopener">GitHub</a><a href="https://github.com/moby/moby" target="_blank" rel="noopener"> </a> 上进行维护。Docker 项目后来还加入了 Linux 基金会，并成立推动 <a href="https://www.opencontainers.org/" target="_blank" rel="noopener">开放容器联盟（OCI）</a><a href="https://www.opencontainers.org/" target="_blank" rel="noopener"> </a>。</p>
<p>Docker 自开源后受到广泛的关注和讨论，至今其 GitHub 项目已经超过 4 万 6 千个星标和一万多个 fork。甚至由于 Docker 项目的火爆，在 2013 年底，<a href="https://blog.docker.com/2013/10/dotcloud-is-becoming-docker-inc/" target="_blank" rel="noopener">dotCloud 公司决定改名为 Docker</a><a href="https://blog.docker.com/2013/10/dotcloud-is-becoming-docker-inc/" target="_blank" rel="noopener"> </a>。Docker 最初是在 Ubuntu 12.04 上开发实现的；Red Hat 则从 RHEL 6.5 开始对 Docker 进行支持；Google 也在其 PaaS 产品中广泛应用 Docker。</p>
<p>Docker 使用 Google 公司推出的 <a href="https://golang.org/" target="_blank" rel="noopener">Go 语言</a><a href="https://golang.org/" target="_blank" rel="noopener"> </a> 进行开发实现，基于 Linux 内核的 <a href="https://zh.wikipedia.org/wiki/Cgroups" target="_blank" rel="noopener">cgroup</a><a href="https://zh.wikipedia.org/wiki/Cgroups" target="_blank" rel="noopener"> </a>，<a href="https://en.wikipedia.org/wiki/Linux_namespaces" target="_blank" rel="noopener">namespace</a><a href="https://en.wikipedia.org/wiki/Linux_namespaces" target="_blank" rel="noopener"> </a>，以及  <a href="https://en.wikipedia.org/wiki/Aufs" target="_blank" rel="noopener">AUFS</a><a href="https://en.wikipedia.org/wiki/Aufs" target="_blank" rel="noopener"> </a> 类的 <a href="https://en.wikipedia.org/wiki/Union_mount" target="_blank" rel="noopener">Union FS</a><a href="https://en.wikipedia.org/wiki/Union_mount" target="_blank" rel="noopener"> </a> 等技术，对进程进行封装隔离，属于 <a href="https://en.wikipedia.org/wiki/Operating-system-level_virtualization" target="_blank" rel="noopener">操作系统层面的虚拟化技术</a><a href="https://en.wikipedia.org/wiki/Operating-system-level_virtualization" target="_blank" rel="noopener"> </a>。由于隔离的进程独立于宿主和其它的隔离的进程，因此也称其为容器。最初实现是基于 <a href="https://linuxcontainers.org/lxc/introduction/" target="_blank" rel="noopener">LXC</a><a href="https://linuxcontainers.org/lxc/introduction/" target="_blank" rel="noopener"> </a>，从 0.7 版本以后开始去除 LXC，转而使用自行开发的 <a href="https://github.com/docker/libcontainer" target="_blank" rel="noopener">libcontainer</a><a href="https://github.com/docker/libcontainer" target="_blank" rel="noopener"> </a>，从 1.11 开始，则进一步演进为使用 <a href="https://github.com/opencontainers/runc" target="_blank" rel="noopener">runC</a><a href="https://github.com/opencontainers/runc" target="_blank" rel="noopener"> </a> 和 <a href="https://github.com/containerd/containerd" target="_blank" rel="noopener">containerd</a><a href="https://github.com/containerd/containerd" target="_blank" rel="noopener"> </a>。</p>
<p>Docker 在容器的基础上，进行了进一步的封装，从文件系统、网络互联到进程隔离等等，极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。</p>
<p>下面的图片比较了  Docker  和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。</p>
<p><img src="/myblog/2020/03/30/Docker/virtualization.png" alt="传统虚拟化"></p>
<p><img src="/myblog/2020/03/30/Docker/docker.png" alt="Docker"></p>
<h2 id="为什么要使用-Docker"><a href="#为什么要使用-Docker" class="headerlink" title="为什么要使用 Docker"></a>为什么要使用 Docker</h2><p>作为一种新兴的虚拟化方式，Docker 跟传统的虚拟化方式相比具有众多的优势。</p>
<p><strong>更高效的利用系统资源</strong></p>
<p>由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销，Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度，都要比传统虚拟机技术更高效。因此，相比虚拟机技术，一个相同配置的主机，往往可以运行更多数量的应用。</p>
<p><strong>更快速的启动时间</strong></p>
<p>传统的虚拟机技术启动应用服务往往需要数分钟，而 Docker 容器应用，由于直接运行于宿主内核，无需启动完整的操作系统，因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。</p>
<p><strong>一致的运行环境</strong></p>
<p>开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致，导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 <em>「这段代码在我机器上没问题啊」</em> 这类问题。</p>
<p><strong>持续交付和部署</strong></p>
<p>对开发和运维（<a href="https://zh.wikipedia.org/wiki/DevOps" target="_blank" rel="noopener">DevOps</a>）人员来说，最希望的就是一次创建或配置，可以在任意地方正常运行。</p>
<p>使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 <code>Dockerfile</code> 来进行镜像构建，并结合 <a href="https://en.wikipedia.org/wiki/Continuous_integration" target="_blank" rel="noopener">持续集成(Continuous Integration)</a></p>
<p>系统进行集成测试，而运维人员则可以直接在生产环境中快速部署该镜像，甚至结合持续部署(Continuous Delivery/Deployment)系统进行自动部署。</p>
<p>而且使用 <code>Dockerfile</code> 使镜像构建透明化，不仅仅开发团队可以理解应用运行环境，也方便运维团队理解应用运行所需条件，帮助更好的生产环境中部署该镜像。</p>
<p><strong>更轻松的迁移</strong></p>
<p>由于  Docker 确保了执行环境的一致性，使得应用的迁移更加容易。Docker  可以在很多平台上运行，无论是物理机、虚拟机、公有云、私有云，甚至是笔记本，其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。</p>
<p><strong>更轻松的维护和扩展</strong></p>
<p>Docker 使用的分层存储以及镜像的技术，使得应用重复部分的复用更为容易，也使得应用的维护更新更加简单，基于基础镜像进一步扩展镜像也变得非常简单。此外，Docker 团队同各个开源项目团队一起维护了一大批高质量的 <a href="https://store.docker.com/search?q=&source=verified&type=image" target="_blank" rel="noopener">官方镜像</a>，既可以直接在生产环境使用，又可以作为基础进一步定制，大大的降低了应用服务的镜像制作成本。</p>
<p><strong>对比传统虚拟机总结</strong></p>
<table>
<thead>
<tr>
<th>特性</th>
<th>容器</th>
<th>虚拟机</th>
</tr>
</thead>
<tbody><tr>
<td>启动</td>
<td>秒级</td>
<td>分钟级</td>
</tr>
<tr>
<td>硬盘使用</td>
<td>一般为 <code>MB</code></td>
<td>一般为 <code>GB</code></td>
</tr>
<tr>
<td>性能</td>
<td>接近原生</td>
<td>弱于</td>
</tr>
<tr>
<td>系统支持量</td>
<td>单机支持上千个容器</td>
<td>一般几十个</td>
</tr>
</tbody></table>
<h2 id="Docker-基本概念"><a href="#Docker-基本概念" class="headerlink" title="Docker 基本概念"></a>Docker 基本概念</h2><p>Docker 包括三个基本概念</p>
<ul>
<li>镜像（<code>Image</code>）</li>
<li>容器（<code>Container</code>）</li>
<li>仓库（<code>Repository</code>）</li>
</ul>
<p>理解了这三个概念，就理解了 Docker 的整个生命周期。</p>
<h2 id="Docker-引擎"><a href="#Docker-引擎" class="headerlink" title="Docker 引擎"></a>Docker 引擎</h2><p>Docker 引擎是一个包含以下主要组件的客户端服务器应用程序。</p>
<ul>
<li>一种服务器，它是一种称为守护进程并且长时间运行的程序。</li>
<li>REST API用于指定程序可以用来与守护进程通信的接口，并指示它做什么。</li>
<li>一个有命令行界面 (CLI) 工具的客户端。</li>
</ul>
<p>Docker 引擎组件的流程如下图所示：</p>
<p><img src="/myblog/2020/03/30/Docker/620140640_31678.png" alt="img"></p>
<h2 id="Docker-系统架构"><a href="#Docker-系统架构" class="headerlink" title="Docker 系统架构"></a>Docker 系统架构</h2><p>Docker 使用客户端-服务器 (C/S) 架构模式，使用远程 API 来管理和创建 Docker 容器。</p>
<p>Docker 容器通过 Docker 镜像来创建。</p>
<p>容器与镜像的关系类似于面向对象编程中的对象与类。</p>
<table>
<thead>
<tr>
<th>Docker</th>
<th>面向对象</th>
</tr>
</thead>
<tbody><tr>
<td>容器</td>
<td>对象</td>
</tr>
<tr>
<td>镜像</td>
<td>类</td>
</tr>
</tbody></table>
<p><img src="/myblog/2020/03/30/Docker/262150629_86976.png" alt="img"></p>
<table>
<thead>
<tr>
<th>标题</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>镜像(Images)</td>
<td>Docker 镜像是用于创建 Docker 容器的模板。</td>
</tr>
<tr>
<td>容器(Container)</td>
<td>容器是独立运行的一个或一组应用。</td>
</tr>
<tr>
<td>客户端(Client)</td>
<td>Docker 客户端通过命令行或者其他工具使用 Docker API (<a href="https://docs.docker.com/reference/api/docker_remote_api" target="_blank" rel="noopener">https://docs.docker.com/reference/api/docker_remote_api</a><a href="https://docs.docker.com/reference/api/docker_remote_api" target="_blank" rel="noopener"> </a>) 与 Docker 的守护进程通信。</td>
</tr>
<tr>
<td>主机(Host)</td>
<td>一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。</td>
</tr>
<tr>
<td>仓库(Registry)</td>
<td>Docker 仓库用来保存镜像，可以理解为代码控制中的代码仓库。Docker Hub(<a href="https://hub.docker.com/" target="_blank" rel="noopener">https://hub.docker.com</a><a href="https://hub.docker.com/" target="_blank" rel="noopener"> </a>) 提供了庞大的镜像集合供使用。</td>
</tr>
<tr>
<td>Docker Machine</td>
<td>Docker Machine是一个简化Docker安装的命令行工具，通过一个简单的命令行即可在相应的平台上安装Docker，比如VirtualBox、 Digital Ocean、Microsoft Azure。</td>
</tr>
</tbody></table>
<h2 id="Docker-镜像"><a href="#Docker-镜像" class="headerlink" title="Docker 镜像"></a>Docker 镜像</h2><p>我们都知道，操作系统分为内核和用户空间。对于 Linux 而言，内核启动后，会挂载 <code>root</code> 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 <code>root</code> 文件系统。比如官方镜像 <code>ubuntu:16.04</code> 就包含了完整的一套 Ubuntu 16.04 最小系统的 <code>root</code> 文件系统。</p>
<p>Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。镜像不包含任何动态数据，其内容在构建之后也不会被改变。</p>
<p><strong>分层存储</strong></p>
<p>因为镜像包含操作系统完整的 root 文件系统，其体积往往是庞大的，因此在 Docker 设计时，就充分利用 Union FS的技术，将其设计为分层存储的架构。所以严格来说，镜像并非是像一个 ISO 那样的打包文件，镜像只是一个虚拟的概念，其实际体现并非由一个文件组成，而是由一组文件系统组成，或者说，由多层文件系统联合组成。</p>
<p>镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。</p>
<p>分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。</p>
<h2 id="Docker-容器"><a href="#Docker-容器" class="headerlink" title="Docker 容器"></a>Docker 容器</h2><p>镜像（<code>Image</code>）和容器（<code>Container</code>）的关系，就像是面向对象程序设计中的 <code>类</code> 和 <code>实例</code> 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。</p>
<p>容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 <a href="https://en.wikipedia.org/wiki/Linux_namespaces" target="_blank" rel="noopener">命名空间</a><a href="https://en.wikipedia.org/wiki/Linux_namespaces" target="_blank" rel="noopener"> </a>。因此容器可以拥有自己的 <code>root</code>文件系统、自己的网络配置、自己的进程空间，甚至自己的用户 ID空间。容器内的进程是运行在一个隔离的环境里，使用起来，就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性，很多人初学Docker 时常常会混淆容器和虚拟机。</p>
<p>前面讲过镜像使用的是分层存储，容器也是如此。每一个容器运行时，是以镜像为基础层，在其上创建一个当前容器的存储层，我们可以称这个为容器运行时读写而准备的存储层为<strong>容器存储层</strong>。</p>
<p>容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。</p>
<p>按照 Docker 最佳实践的要求，容器不应该向其存储层内写入任何数据，容器存储层要保持无状态化。所有的文件写入操作，都应该使用 <code>数据卷（Volume）</code>、或者绑定宿主目录，在这些位置的读写会跳过容器存储层，直接对宿主（或网络存储）发生读写，其性能和稳定性更高。</p>
<p>数据卷的生存周期独立于容器，容器消亡，数据卷不会消亡。因此，使用数据卷后，容器删除或者重新运行之后，数据却不会丢失。</p>
<h2 id="Docker-仓库"><a href="#Docker-仓库" class="headerlink" title="Docker 仓库"></a>Docker 仓库</h2><p>镜像构建完成后，可以很容易的在当前宿主机上运行，但是，如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，<code>Docker Registry</code> 就是这样的服务。</p>
<p>一个 <strong>Docker Registry</strong> 中可以包含多个<strong>仓库</strong>（<code>Repository</code>）；每个仓库可以包含多个<strong>标签</strong>（<code>Tag</code>）；每个标签对应一个镜像。</p>
<p>通常，一个仓库会包含同一个软件不同版本的镜像，而标签就常用于对应该软件的各个版本。我们可以通过 <code>&lt;仓库名&gt;:&lt;标签&gt;</code> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签，将以 <code>latest</code> 作为默认标签。</p>
<p>以 <a href="https://store.docker.com/images/ubuntu" target="_blank" rel="noopener">Ubuntu 镜像</a> 为例，<code>ubuntu</code> 是仓库的名字，其内包含有不同的版本标签，如，<code>14.04</code>, <code>16.04</code>。我们可以通过 <code>ubuntu:14.04</code>，或者 <code>ubuntu:16.04</code> 来具体指定所需哪个版本的镜像。如果忽略了标签，比如 <code>ubuntu</code>，那将视为 <code>ubuntu:latest</code>。</p>
<p>仓库名经常以 <em>两段式路径</em> 形式出现，比如 <code>jwilder/nginx-proxy</code>，前者往往意味着 Docker Registry 多用户环境下的用户名，后者则往往是对应的软件名。但这并非绝对，取决于所使用的具体 Docker Registry 的软件或服务。</p>
<h3 id="公有-Docker-Registry"><a href="#公有-Docker-Registry" class="headerlink" title="公有 Docker Registry"></a>公有 Docker Registry</h3><p>Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像，并可能提供收费服务供用户管理私有镜像。</p>
<p>最常使用的 Registry 公开服务是官方的 <a href="https://hub.docker.com/" target="_blank" rel="noopener">Docker Hub</a><a href="https://hub.docker.com/" target="_blank" rel="noopener"> </a>，这也是默认的 Registry，并拥有大量的高质量的官方镜像。除此以外，还有 <a href="https://coreos.com/" target="_blank" rel="noopener">CoreOS</a><a href="https://coreos.com/" target="_blank" rel="noopener"> </a> 的 <a href="https://quay.io/repository/" target="_blank" rel="noopener">Quay.io</a><a href="https://quay.io/repository/" target="_blank" rel="noopener"> </a>，CoreOS 相关的镜像存储在这里；Google 的 <a href="https://cloud.google.com/container-registry/" target="_blank" rel="noopener">Google Container Registry</a><a href="https://cloud.google.com/container-registry/" target="_blank" rel="noopener"> </a>，<a href="http://kubernetes.io/" target="_blank" rel="noopener">Kubernetes</a><a href="http://kubernetes.io/" target="_blank" rel="noopener"> </a> 的镜像使用的就是这个服务。</p>
<p>由于某些原因，在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对 Docker Hub 的镜像服务（<code>Registry Mirror</code>），这些镜像服务被称为<strong>加速器</strong>。常见的有 <a href="https://cr.console.aliyun.com/#/accelerator" target="_blank" rel="noopener">阿里云加速器</a><a href="https://cr.console.aliyun.com/#/accelerator" target="_blank" rel="noopener"> </a>、<a href="https://www.daocloud.io/mirror#accelerator-doc" target="_blank" rel="noopener">DaoCloud 加速器</a><a href="https://www.daocloud.io/mirror#accelerator-doc" target="_blank" rel="noopener"> </a> 等。使用加速器会直接从国内的地址下载 Docker Hub 的镜像，比直接从 Docker Hub 下载速度会提高很多。</p>
<p>国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 <a href="https://hub.tenxcloud.com/" target="_blank" rel="noopener">时速云镜像仓库</a><a href="https://hub.tenxcloud.com/" target="_blank" rel="noopener"> </a>、<a href="https://c.163.com/hub#/m/library/" target="_blank" rel="noopener">网易云镜像服务</a><a href="https://c.163.com/hub#/m/library/" target="_blank" rel="noopener"> </a>、<a href="https://hub.daocloud.io/" target="_blank" rel="noopener">DaoCloud 镜像市场</a><a href="https://hub.daocloud.io/" target="_blank" rel="noopener"> </a>、<a href="https://cr.console.aliyun.com" target="_blank" rel="noopener">阿里云镜像库</a><a href="https://cr.console.aliyun.com" target="_blank" rel="noopener"> </a> 等。</p>
<h3 id="私有-Docker-Registry"><a href="#私有-Docker-Registry" class="headerlink" title="私有 Docker Registry"></a>私有 Docker Registry</h3><p>除了使用公开服务外，用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 <a href="https://store.docker.com/images/registry/" target="_blank" rel="noopener">Docker Registry</a><a href="https://store.docker.com/images/registry/" target="_blank" rel="noopener"> </a> 镜像，可以直接使用做为私有 Registry 服务。</p>
<p>开源的 Docker Registry 镜像只提供了 <a href="https://docs.docker.com/registry/spec/api/" target="_blank" rel="noopener">Docker Registry API</a><a href="https://docs.docker.com/registry/spec/api/" target="_blank" rel="noopener"> </a> 的服务端实现，足以支持 <code>docker</code> 命令，不影响使用。但不包含图形界面，以及镜像维护、用户管理、访问控制等高级功能。在官方的商业化版本 <a href="https://docs.docker.com/datacenter/dtr/2.0/" target="_blank" rel="noopener">Docker Trusted Registry</a><a href="https://docs.docker.com/datacenter/dtr/2.0/" target="_blank" rel="noopener"> </a> 中，提供了这些高级功能。</p>
<p>除了官方的 Docker Registry 外，还有第三方软件实现了 Docker Registry API，甚至提供了用户界面以及一些高级功能。比如，<a href="https://github.com/vmware/harbor" target="_blank" rel="noopener">VMWare Harbor</a><a href="https://github.com/vmware/harbor" target="_blank" rel="noopener"> </a> 和 <a href="https://www.sonatype.com/docker" target="_blank" rel="noopener">Sonatype Nexus</a><a href="https://www.sonatype.com/docker" target="_blank" rel="noopener"> </a>。</p>
<h2 id="安装-Docker"><a href="#安装-Docker" class="headerlink" title="安装 Docker"></a>安装 Docker</h2><p>Docker 在 1.13 版本之后，从 2017 年的 3 月 1 日开始，版本命名规则变为如下：</p>
<table>
<thead>
<tr>
<th>项目</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>版本格式</td>
<td>YY.MM</td>
</tr>
<tr>
<td>Stable 版本</td>
<td>每个季度发行</td>
</tr>
<tr>
<td>Edge 版本</td>
<td>每个月发行</td>
</tr>
</tbody></table>
<p>同时 Docker 划分为 CE 和 EE。CE 即社区版（免费，支持周期三个月），EE 即企业版，强调安全，付费使用。</p>
<p>Docker CE 每月发布一个 Edge 版本 (17.03, 17.04, 17.05…)，每三个月发布一个 Stable 版本 (17.03, 17.06, 17.09…)，Docker EE 和 Stable 版本号保持一致，但每个版本提供一年维护。</p>
<p>官方网站上有各种环境下的 <a href="https://docs.docker.com/engine/installation/" target="_blank" rel="noopener">安装指南</a><a href="https://docs.docker.com/engine/installation/" target="_blank" rel="noopener"> </a>，这里主要介绍 Docker CE 在 Linux 、Windows 10 (PC) 和 macOS 上的安装。</p>
<p>也可以从<a href="http://www.docker.org.cn/index.html" target="_blank" rel="noopener">Docker中国</a>学习</p>
<h2 id="Centos7-安装-Docker"><a href="#Centos7-安装-Docker" class="headerlink" title="Centos7 安装 Docker"></a>Centos7 安装 Docker</h2><p>1、Docker 要求 CentOS 系统的内核版本高于 3.10 ，查看本页面的前提条件来验证你的CentOS 版本是否支持 Docker 。</p>
<p>通过 <strong>uname -r</strong> 命令查看你当前的内核版本</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ uname -r</span><br></pre></td></tr></table></figure>

<p>2、使用 <code>root</code> 权限登录 Centos。确保 yum 包更新到最新。</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum update</span><br></pre></td></tr></table></figure>

<p>3、旧版本的 Docker 称为 <code>docker</code> 或者 <code>docker-engine</code>，使用以下命令卸载旧版本：</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum remove docker  docker-common docker-selinux docker-engine</span><br></pre></td></tr></table></figure>

<p>或者</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum remove docker \</span><br><span class="line">                  docker-client \</span><br><span class="line">                  docker-client-latest \</span><br><span class="line">                  docker-common \</span><br><span class="line">                  docker-latest \</span><br><span class="line">                  docker-latest-logrotate \</span><br><span class="line">                  docker-logrotate \</span><br><span class="line">                  docker-selinux \</span><br><span class="line">                  docker-engine-selinux \</span><br><span class="line">                  docker-engine</span><br></pre></td></tr></table></figure>

<p>4、安装需要的软件包， yum-util 提供yum-config-manager功能，另外两个是devicemapper驱动依赖的</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2</span><br></pre></td></tr></table></figure>

<p>5、设置yum源</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo</span><br><span class="line"></span><br><span class="line">或者</span><br><span class="line">$ sudo yum-config-manager \</span><br><span class="line">    --add-repo \</span><br><span class="line">    https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo</span><br><span class="line">    </span><br><span class="line"><span class="comment"># 官方源</span></span><br><span class="line"><span class="comment"># $ sudo yum-config-manager \</span></span><br><span class="line"><span class="comment">#     --add-repo \</span></span><br><span class="line"><span class="comment">#     https://download.docker.com/linux/centos/docker-ce.repo</span></span><br></pre></td></tr></table></figure>

<p><img src="/myblog/2020/03/30/Docker/1107037-20180128094640209-1433322312.png" alt="img"></p>
<p>6、可以查看所有仓库中所有docker版本，并选择特定版本安装</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yum list docker-ce --showduplicates | sort -r</span><br></pre></td></tr></table></figure>

<p><img src="/myblog/2020/03/30/Docker/1107037-20180128095038600-772177322.png" alt="img"></p>
<p>7、安装docker</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum install docker-ce  <span class="comment">#由于repo中默认只开启stable仓库，故这里安装的是最新稳定版17.12.0</span></span><br><span class="line">$ sudo yum install &lt;FQPN&gt;  <span class="comment"># 例如：sudo yum install docker-ce-17.12.0.ce</span></span><br></pre></td></tr></table></figure>

<p> <img src="/myblog/2020/03/30/Docker/1107037-20180128103448287-493824081.png" alt="img"></p>
<p>8、启动并加入开机启动</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo systemctl start docker</span><br><span class="line">$ sudo systemctl <span class="built_in">enable</span> docker</span><br></pre></td></tr></table></figure>

<p>9、验证安装是否成功(有client和service两部分表示docker安装启动都成功了)</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker version</span><br></pre></td></tr></table></figure>

<p><img src="/myblog/2020/03/30/Docker/1107037-20180128104046600-1053107877.png" alt="img"></p>
<p>或者</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># step 1: 安装必要的一些系统工具</span></span><br><span class="line">sudo yum install -y yum-utils device-mapper-persistent-data lvm2</span><br><span class="line"><span class="comment"># Step 2: 添加软件源信息</span></span><br><span class="line">sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo</span><br><span class="line"><span class="comment"># Step 3: 更新并安装 Docker-CE</span></span><br><span class="line">sudo yum makecache fast</span><br><span class="line">sudo yum -y install docker-ce</span><br><span class="line"><span class="comment"># Step 4: 开启Docker服务</span></span><br><span class="line">sudo service docker start</span><br><span class="line"></span><br><span class="line">注意：其他注意事项在下面的注释中</span><br><span class="line"><span class="comment"># 官方软件源默认启用了最新的软件，您可以通过编辑软件源的方式获取各个版本的软件包。例如官方并没有将测试版本的软件源置为可用，你可以通过以下方式开启。同理可以开启各种测试版本等。</span></span><br><span class="line"><span class="comment"># vim /etc/yum.repos.d/docker-ce.repo</span></span><br><span class="line"><span class="comment">#   将 [docker-ce-test] 下方的 enabled=0 修改为 enabled=1</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># 安装指定版本的Docker-CE:</span></span><br><span class="line"><span class="comment"># Step 1: 查找Docker-CE的版本:</span></span><br><span class="line"><span class="comment"># yum list docker-ce.x86_64 --showduplicates | sort -r</span></span><br><span class="line"><span class="comment">#   Loading mirror speeds from cached hostfile</span></span><br><span class="line"><span class="comment">#   Loaded plugins: branch, fastestmirror, langpacks</span></span><br><span class="line"><span class="comment">#   docker-ce.x86_64            17.03.1.ce-1.el7.centos            docker-ce-stable</span></span><br><span class="line"><span class="comment">#   docker-ce.x86_64            17.03.1.ce-1.el7.centos            @docker-ce-stable</span></span><br><span class="line"><span class="comment">#   docker-ce.x86_64            17.03.0.ce-1.el7.centos            docker-ce-stable</span></span><br><span class="line"><span class="comment">#   Available Packages</span></span><br><span class="line"><span class="comment"># Step2 : 安装指定版本的Docker-CE: (VERSION 例如上面的 17.03.0.ce.1-1.el7.centos)</span></span><br><span class="line"><span class="comment"># sudo yum -y install docker-ce-[VERSION]</span></span><br><span class="line"><span class="comment"># 注意：在某些版本之后，docker-ce安装出现了其他依赖包，如果安装失败的话请关注错误信息。例如 docker-ce 17.03 之后，需要先安装 docker-ce-selinux。</span></span><br><span class="line"><span class="comment"># yum list docker-ce-selinux- --showduplicates | sort -r</span></span><br><span class="line"><span class="comment"># sudo yum -y install docker-ce-selinux-[VERSION]</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 通过经典网络、VPC网络内网安装时，用以下命令替换Step 2中的命令</span></span><br><span class="line"><span class="comment"># 经典网络：</span></span><br><span class="line"><span class="comment"># sudo yum-config-manager --add-repo http://mirrors.aliyuncs.com/docker-ce/linux/centos/docker-ce.repo</span></span><br><span class="line"><span class="comment"># VPC网络：</span></span><br><span class="line"><span class="comment"># sudo yum-config-manager --add-repo http://mirrors.could.aliyuncs.com/docker-ce/linux/centos/docker-ce.repo</span></span><br></pre></td></tr></table></figure>

<p><strong>问题</strong></p>
<p>1、因为之前已经安装过旧版本的docker，在安装的时候报错如下：</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Transaction check error:</span><br><span class="line">  file /usr/bin/docker from install of docker-ce-17.12.0.ce-1.el7.centos.x86_64 conflicts with file from package docker-common-2:1.12.6-68.gitec8512b.el7.centos.x86_64</span><br><span class="line">  file /usr/bin/docker-containerd from install of docker-ce-17.12.0.ce-1.el7.centos.x86_64 conflicts with file from package docker-common-2:1.12.6-68.gitec8512b.el7.centos.x86_64</span><br><span class="line">  file /usr/bin/docker-containerd-shim from install of docker-ce-17.12.0.ce-1.el7.centos.x86_64 conflicts with file from package docker-common-2:1.12.6-68.gitec8512b.el7.centos.x86_64</span><br><span class="line">  file /usr/bin/dockerd from install of docker-ce-17.12.0.ce-1.el7.centos.x86_64 conflicts with file from package docker-common-2:1.12.6-68.gitec8512b.el7.centos.x86_64</span><br></pre></td></tr></table></figure>

<p>2、卸载旧版本的包</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum erase docker-common-2:1.12.6-68.gitec8512b.el7.centos.x86_64</span><br></pre></td></tr></table></figure>

<p><img src="/myblog/2020/03/30/Docker/1107037-20180128103145287-536100760.png" alt="img"></p>
<p>3、再次安装docker</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum install docker-ce</span><br></pre></td></tr></table></figure>

<p> <strong>镜像加速</strong></p>
<p>鉴于国内网络问题，后续拉取 Docker 镜像十分缓慢，强烈建议安装 Docker 之后配置 <code>国内镜像加速</code>。</p>
<p><strong>添加内核参数</strong></p>
<p>默认配置下，如果在 CentOS 使用 Docker CE 看到下面的这些警告信息：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">WARNING: bridge-nf-call-iptables is disabled</span><br><span class="line">WARNING: bridge-nf-call-ip6tables is disabled</span><br></pre></td></tr></table></figure>

<p>请添加内核配置参数以启用这些功能。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ sudo tee -a /etc/sysctl.conf &lt;&lt;-EOF</span><br><span class="line">net.bridge.bridge-nf-call-ip6tables = 1</span><br><span class="line">net.bridge.bridge-nf-call-iptables = 1</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure>

<p>然后重新加载 <code>sysctl.conf</code> 即可</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo sysctl -p</span><br></pre></td></tr></table></figure>

<h2 id="Docker-镜像加速器"><a href="#Docker-镜像加速器" class="headerlink" title="Docker 镜像加速器"></a>Docker 镜像加速器</h2><p>国内从 Docker Hub 拉取镜像有时会遇到困难，此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务，例如：</p>
<ul>
<li><p><a href="https://docs.docker.com/registry/recipes/mirror/#use-case-the-china-registry-mirror" target="_blank" rel="noopener">Docker 官方提供的中国 registry mirror</a><a href="https://docs.docker.com/registry/recipes/mirror/#use-case-the-china-registry-mirror" target="_blank" rel="noopener"> </a></p>
</li>
<li><p><a href="https://cr.console.aliyun.com/#/accelerator" target="_blank" rel="noopener">阿里云加速器</a><a href="https://cr.console.aliyun.com/#/accelerator" target="_blank" rel="noopener"> </a></p>
</li>
<li><p><a href="https://www.daocloud.io/mirror#accelerator-doc" target="_blank" rel="noopener">DaoCloud 加速器</a><a href="https://www.daocloud.io/mirror#accelerator-doc" target="_blank" rel="noopener"> </a></p>
<p>我们以 Docker 官方加速器为例进行介绍。</p>
</li>
</ul>
<p><strong>Ubuntu 14.04、Debian 7 Wheezy</strong></p>
<p>对于使用 <a href="http://upstart.ubuntu.com/" target="_blank" rel="noopener">upstart</a> 的系统而言，编辑 <code>/etc/default/docker</code> 文件，在其中的 <code>DOCKER_OPTS</code> 中配置加速器地址：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DOCKER_OPTS=<span class="string">"--registry-mirror=https://registry.docker-cn.com"</span></span><br></pre></td></tr></table></figure>

<p>重新启动服务。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo service docker restart</span><br></pre></td></tr></table></figure>

<p><strong>Ubuntu 16.04+、Debian 8+、CentOS 7</strong></p>
<p>对于使用 <a href="https://www.freedesktop.org/wiki/Software/systemd/" target="_blank" rel="noopener">systemd</a>的系统，请在 <code>/etc/docker/daemon.json</code> 中写入如下内容（如果文件不存在请新建该文件）</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"registry-mirrors"</span>: [<span class="string">"https://1sbmxpab.mirror.aliyuncs.com"</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意，一定要保证该文件符合 json 规范，否则 Docker 将不能启动。</p>
</blockquote>
<p>之后重新启动服务。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo systemctl daemon-reload</span><br><span class="line">$ sudo systemctl restart docker</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意：如果您之前查看旧教程，修改了 <code>docker.service</code> 文件内容，请去掉您添加的内容（<code>--registry-mirror=https://registry.docker-cn.com</code>），这里不再赘述。</p>
</blockquote>
<p><strong>Windows 10</strong></p>
<p>对于使用 Windows 10 的系统，在系统右下角托盘 Docker 图标内右键菜单选择 <code>Settings</code>，打开配置窗口后左侧导航菜单选择 <code>Daemon</code>。在 <code>Registry mirrors</code> 一栏中填写加速器地址 <code>https://registry.docker-cn.com</code>，之后点击 <code>Apply</code> 保存后 Docker 就会重启并应用配置的镜像地址了。</p>
<p>macOS</p>
<p>对于使用 macOS 的用户，在任务栏点击 Docker for mac 应用图标 -&gt; Perferences… -&gt; Daemon -&gt; Registry mirrors。在列表中填写加速器地址 <code>https://registry.docker-cn.com</code>。修改完成之后，点击 <code>Apply &amp; Restart</code> 按钮，Docker 就会重启并应用配置的镜像地址了。</p>
<p><strong>检查加速器是否生效</strong></p>
<p>配置加速器之后，如果拉取镜像仍然十分缓慢，请手动检查加速器配置是否生效，在命令行执行 <code>docker info</code>，如果从结果中看到了如下内容，说明配置成功。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Registry Mirrors:</span><br><span class="line"> https://registry.docker-cn.com/</span><br></pre></td></tr></table></figure>

<h2 id="使用-Docker-镜像"><a href="#使用-Docker-镜像" class="headerlink" title="使用 Docker 镜像"></a>使用 Docker 镜像</h2><p>在之前的介绍中，我们知道镜像是 Docker 的三大组件之一。</p>
<p>Docker 运行容器前需要本地存在对应的镜像，如果本地不存在该镜像，Docker 会从镜像仓库下载该镜像。</p>
<p>本章将介绍更多关于镜像的内容，包括：</p>
<ul>
<li>从仓库获取镜像；</li>
<li>管理本地主机上的镜像；</li>
<li>介绍镜像实现的基本原理。</li>
</ul>
<h2 id="Docker-获取镜像"><a href="#Docker-获取镜像" class="headerlink" title="Docker 获取镜像"></a>Docker 获取镜像</h2><p>之前提到过，<a href="https://hub.docker.com/explore/" target="_blank" rel="noopener">Docker Hub</a><a href="https://hub.docker.com/explore/" target="_blank" rel="noopener"> </a> 上有大量的高质量的镜像可以用，这里我们就说一下怎么获取这些镜像。</p>
<p>在拉去镜像之前可以查询通过<a href="https://hub.docker.com/" target="_blank" rel="noopener">docker hub</a>也可以使用命令</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker search mysql</span><br></pre></td></tr></table></figure>

<p>从 Docker 镜像仓库获取镜像的命令是 <code>docker pull</code>。其命令格式为：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]</span><br></pre></td></tr></table></figure>

<p>具体的选项可以通过 <code>docker pull --help</code> 命令看到，这里我们说一下镜像名称的格式。</p>
<ul>
<li>Docker 镜像仓库地址：地址的格式一般是 <code>&lt;域名/IP&gt;[:端口号]</code>。默认地址是 Docker Hub。</li>
<li>仓库名：如之前所说，这里的仓库名是两段式名称，即 <code>&lt;用户名&gt;/&lt;软件名&gt;</code>。对于 Docker Hub，如果不给出用户名，则默认为 <code>library</code>，也就是官方镜像。</li>
</ul>
<p>比如：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ docker pull ubuntu:16.04</span><br><span class="line">16.04: Pulling from library/ubuntu</span><br><span class="line">bf5d46315322: Pull complete</span><br><span class="line">9f13e0ac480c: Pull complete</span><br><span class="line">e8988b5b3097: Pull complete</span><br><span class="line">40af181810e7: Pull complete</span><br><span class="line">e6f7c7e5c03e: Pull complete</span><br><span class="line">Digest: sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe</span><br><span class="line">Status: Downloaded newer image <span class="keyword">for</span> ubuntu:16.04</span><br></pre></td></tr></table></figure>

<p>上面的命令中没有给出 Docker 镜像仓库地址，因此将会从 Docker Hub 获取镜像。而镜像名称是 <code>ubuntu:16.04</code>，因此将会获取官方镜像 <code>library/ubuntu</code> 仓库中标签为 <code>16.04</code> 的镜像。</p>
<p>在使用上面命令的时候，你可能会发现，你所看到的层 ID 以及 <code>sha256</code> 的摘要和这里的不一样。这是因为官方镜像是一直在维护的，有任何新的 bug，或者版本更新，都会进行修复再以原来的标签发布，这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像。</p>
<h3 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h3><p>有了镜像后，我们就能够以这个镜像为基础启动并运行一个容器。以上面的 <code>ubuntu:16.04</code> 为例，如果我们打算启动里面的 <code>bash</code> 并且进行交互式操作的话，可以执行下面的命令。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -it --rm \</span><br><span class="line">    ubuntu:16.04 \</span><br><span class="line">    bash</span><br><span class="line"></span><br><span class="line">root@e7009c6ce357:/<span class="comment"># cat /etc/os-release</span></span><br><span class="line">NAME=<span class="string">"Ubuntu"</span></span><br><span class="line">VERSION=<span class="string">"16.04.4 LTS, Trusty Tahr"</span></span><br><span class="line">ID=ubuntu</span><br><span class="line">ID_LIKE=debian</span><br><span class="line">PRETTY_NAME=<span class="string">"Ubuntu 16.04.4 LTS"</span></span><br><span class="line">VERSION_ID=<span class="string">"16.04"</span></span><br><span class="line">HOME_URL=<span class="string">"http://www.ubuntu.com/"</span></span><br><span class="line">SUPPORT_URL=<span class="string">"http://help.ubuntu.com/"</span></span><br><span class="line">BUG_REPORT_URL=<span class="string">"http://bugs.launchpad.net/ubuntu/"</span></span><br></pre></td></tr></table></figure>

<p><code>docker run</code> 就是运行容器的命令，我们这里简要的说明一下上面用到的参数。</p>
<ul>
<li><code>-it</code>：这是两个参数，一个是 <code>-i</code>：交互式操作，一个是 <code>-t</code> 终端。我们这里打算进入 <code>bash</code> 执行一些命令并查看返回结果，因此我们需要交互式终端。</li>
<li><code>--rm</code>：这个参数是说容器退出后随之将其删除。默认情况下，为了排障需求，退出的容器并不会立即删除，除非手动 <code>docker rm</code>。我们这里只是随便执行个命令，看看结果，不需要排障和保留结果，因此使用 <code>--rm</code> 可以避免浪费空间。</li>
<li><code>ubuntu:16.04</code>：这是指用 <code>ubuntu:16.04</code> 镜像为基础来启动容器。</li>
<li><code>bash</code>：放在镜像名后的是<strong>命令</strong>，这里我们希望有个交互式 Shell，因此用的是 <code>bash</code>。</li>
</ul>
<p>进入容器后，我们可以在 Shell 下操作，执行任何所需的命令。这里，我们执行了 <code>cat /etc/os-release</code>，这是 Linux 常用的查看当前系统版本的命令，从返回的结果可以看到容器内是 <code>Ubuntu 16.04.4 LTS</code> 系统。</p>
<p>最后我们通过 <code>exit</code> 退出了这个容器。</p>
<h2 id="Docker-列出镜像"><a href="#Docker-列出镜像" class="headerlink" title="Docker 列出镜像"></a>Docker 列出镜像</h2><p>要想列出已经下载下来的镜像，可以使用 <code>docker image ls</code> 命令。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls</span><br><span class="line">REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">redis                latest              5f515359c7f8        5 days ago          183 MB</span><br><span class="line">nginx                latest              05a60462f8ba        5 days ago          181 MB</span><br><span class="line">mongo                3.2                 fe9198c04d62        5 days ago          342 MB</span><br><span class="line">&lt;none&gt;               &lt;none&gt;              00285df0df87        5 days ago          342 MB</span><br><span class="line">ubuntu               16.04               f753707788c5        4 weeks ago         127 MB</span><br><span class="line">ubuntu               latest              f753707788c5        4 weeks ago         127 MB</span><br><span class="line">ubuntu               14.04               1e0c3dd64ccd        4 weeks ago         188 MB</span><br></pre></td></tr></table></figure>

<p>列表包含了 <code>仓库名</code>、<code>标签</code>、<code>镜像 ID</code>、<code>创建时间</code> 以及 <code>所占用的空间</code>。</p>
<p>其中仓库名、标签在之前的基础概念章节已经介绍过了。<strong>镜像 ID</strong> 则是镜像的唯一标识，一个镜像可以对应多个<strong>标签</strong>。因此，在上面的例子中，我们可以看到 <code>ubuntu:16.04</code> 和 <code>ubuntu:latest</code> 拥有相同的 ID，因为它们对应的是同一个镜像。</p>
<p><strong>镜像体积</strong></p>
<p>如果仔细观察，会注意到，这里标识的所占用空间和在 Docker Hub 上看到的镜像大小不同。比如，<code>ubuntu:16.04</code> 镜像大小，在这里是 <code>127 MB</code>，但是在 <a href="https://hub.docker.com/r/library/ubuntu/tags/" target="_blank" rel="noopener">Docker Hub</a>显示的却是 <code>50 MB</code>。这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的，因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。而 <code>docker image ls</code> 显示的是镜像下载到本地后，展开的大小，准确说，是展开后的各层所占空间的总和，因为镜像到本地后，查看空间的时候，更关心的是本地磁盘空间占用的大小。</p>
<p>另外一个需要注意的问题是，<code>docker image ls</code>  列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker  镜像是多层存储结构，并且可以继承、复用，因此不同镜像可能会因为使用相同的基础镜像，从而拥有共同的层。由于 Docker 使用 Union  FS，相同的层只需要保存一份即可，因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。</p>
<p>你可以通过以下命令来便捷的查看镜像、容器、数据卷所占用的空间。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ docker system df</span><br><span class="line">TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE</span><br><span class="line">Images              24                  0                   1.992GB             1.992GB (100%)</span><br><span class="line">Containers          1                   0                   62.82MB             62.82MB (100%)</span><br><span class="line">Local Volumes       9                   0                   652.2MB             652.2MB (100%)</span><br><span class="line">Build Cache                                                 0B                  0B</span><br></pre></td></tr></table></figure>

<p><strong>虚悬镜像</strong></p>
<p>上面的镜像列表中，还可以看到一个特殊的镜像，这个镜像既没有仓库名，也没有标签，均为 <code>&lt;none&gt;</code>。：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;none&gt;               &lt;none&gt;              00285df0df87        5 days ago          342 MB</span><br></pre></td></tr></table></figure>

<p>这个镜像原本是有镜像名和标签的，原来为 <code>mongo:3.2</code>，随着官方镜像维护，发布了新版本后，重新 <code>docker pull mongo:3.2</code> 时，<code>mongo:3.2</code> 这个镜像名被转移到了新下载的镜像身上，而旧的镜像上的这个名称则被取消，从而成为了 <code>&lt;none&gt;</code>。除了 <code>docker pull</code> 可能导致这种情况，<code>docker build</code> 也同样可以导致这种现象。由于新旧镜像同名，旧镜像名称被取消，从而出现仓库名、标签均为 <code>&lt;none&gt;</code> 的镜像。这类无标签镜像也被称为 <strong>虚悬镜像(dangling image)</strong> ，可以用下面的命令专门显示这类镜像：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls -f dangling=<span class="literal">true</span></span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">&lt;none&gt;              &lt;none&gt;              00285df0df87        5 days ago          342 MB</span><br></pre></td></tr></table></figure>

<p>一般来说，虚悬镜像已经失去了存在的价值，是可以随意删除的，可以用下面的命令删除。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker image prune</span><br></pre></td></tr></table></figure>

<p><strong>中间层镜像</strong></p>
<p>为了加速镜像构建、重复利用资源，Docker 会利用 <strong>中间层镜像</strong>。所以在使用一段时间后，可能会看到一些依赖的中间层镜像。默认的 <code>docker image ls</code> 列表中只会显示顶层镜像，如果希望显示包括中间层镜像在内的所有镜像的话，需要加 <code>-a</code> 参数。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls -a</span><br></pre></td></tr></table></figure>

<p>这样会看到很多无标签的镜像，与之前的虚悬镜像不同，这些无标签的镜像很多都是中间层镜像，是其它镜像所依赖的镜像。这些无标签镜像不应该删除，否则会导致上层镜像因为依赖丢失而出错。实际上，这些镜像也没必要删除，因为之前说过，相同的层只会存一遍，而这些镜像是别的镜像的依赖，因此并不会因为它们被列出来而多存了一份，无论如何你也会需要它们。只要删除那些依赖它们的镜像后，这些依赖的中间层镜像也会被连带删除。</p>
<p><strong>列出部分镜像</strong></p>
<p>不加任何参数的情况下，<code>docker image ls</code> 会列出所有顶级镜像，但是有时候我们只希望列出部分镜像。<code>docker image ls</code> 有好几个参数可以帮助做到这个事情。</p>
<p>根据仓库名列出镜像</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls ubuntu</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">ubuntu              16.04               f753707788c5        4 weeks ago         127 MB</span><br><span class="line">ubuntu              latest              f753707788c5        4 weeks ago         127 MB</span><br><span class="line">ubuntu              14.04               1e0c3dd64ccd        4 weeks ago         188 MB</span><br></pre></td></tr></table></figure>

<p>列出特定的某个镜像，也就是说指定仓库名和标签</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls ubuntu:16.04</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">ubuntu              16.04               f753707788c5        4 weeks ago         127 MB</span><br></pre></td></tr></table></figure>

<p>除此以外，<code>docker image ls</code> 还支持强大的过滤器参数 <code>--filter</code>，或者简写 <code>-f</code>。之前我们已经看到了使用过滤器来列出虚悬镜像的用法，它还有更多的用法。比如，我们希望看到在 <code>mongo:3.2</code> 之后建立的镜像，可以用下面的命令：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls -f since=mongo:3.2</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">redis               latest              5f515359c7f8        5 days ago          183 MB</span><br><span class="line">nginx               latest              05a60462f8ba        5 days ago          181 MB</span><br></pre></td></tr></table></figure>

<p>想查看某个位置之前的镜像也可以，只需要把 <code>since</code> 换成 <code>before</code> 即可。</p>
<p>此外，如果镜像构建时，定义了 <code>LABEL</code>，还可以通过 <code>LABEL</code> 来过滤。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls -f label=com.example.version=0.1</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p><strong>以特定格式显示</strong></p>
<p>默认情况下，<code>docker image ls</code> 会输出一个完整的表格，但是我们并非所有时候都会需要这些内容。比如，刚才删除虚悬镜像的时候，我们需要利用 <code>docker image ls</code> 把所有的虚悬镜像的 ID 列出来，然后才可以交给 <code>docker image rm</code> 命令作为参数来删除指定的这些镜像，这个时候就用到了 <code>-q</code> 参数。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls -q</span><br><span class="line">5f515359c7f8</span><br><span class="line">05a60462f8ba</span><br><span class="line">fe9198c04d62</span><br><span class="line">00285df0df87</span><br><span class="line">f753707788c5</span><br><span class="line">f753707788c5</span><br><span class="line">1e0c3dd64ccd</span><br></pre></td></tr></table></figure>

<p><code>--filter</code> 配合 <code>-q</code> 产生出指定范围的 ID 列表，然后送给另一个 <code>docker</code> 命令作为参数，从而针对这组实体成批的进行某种操作的做法在 Docker 命令行使用过程中非常常见，不仅仅是镜像，将来我们会在各个命令中看到这类搭配以完成很强大的功能。因此每次在文档看到过滤器后，可以多注意一下它们的用法。</p>
<p>另外一些时候，我们可能只是对表格的结构不满意，希望自己组织列；或者不希望有标题，这样方便其它程序解析结果等，这就用到了 <a href="https://gohugo.io/templates/go-templates/" target="_blank" rel="noopener">Go 的模板语法</a>。</p>
<p>比如，下面的命令会直接列出镜像结果，并且只包含镜像ID和仓库名：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls --format <span class="string">"&#123;&#123;.ID&#125;&#125;: &#123;&#123;.Repository&#125;&#125;"</span></span><br><span class="line">5f515359c7f8: redis</span><br><span class="line">05a60462f8ba: nginx</span><br><span class="line">fe9198c04d62: mongo</span><br><span class="line">00285df0df87: &lt;none&gt;</span><br><span class="line">f753707788c5: ubuntu</span><br><span class="line">f753707788c5: ubuntu</span><br><span class="line">1e0c3dd64ccd: ubuntu</span><br></pre></td></tr></table></figure>

<p>或者打算以表格等距显示，并且有标题行，和默认一样，不过自己定义列：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls --format <span class="string">"table &#123;&#123;.ID&#125;&#125;\t&#123;&#123;.Repository&#125;&#125;\t&#123;&#123;.Tag&#125;&#125;"</span></span><br><span class="line">IMAGE ID            REPOSITORY          TAG</span><br><span class="line">5f515359c7f8        redis               latest</span><br><span class="line">05a60462f8ba        nginx               latest</span><br><span class="line">fe9198c04d62        mongo               3.2</span><br><span class="line">00285df0df87        &lt;none&gt;              &lt;none&gt;</span><br><span class="line">f753707788c5        ubuntu              16.04</span><br><span class="line">f753707788c5        ubuntu              latest</span><br><span class="line">1e0c3dd64ccd        ubuntu              14.04</span><br></pre></td></tr></table></figure>

<h2 id="Docker-删除本地镜像"><a href="#Docker-删除本地镜像" class="headerlink" title="Docker 删除本地镜像"></a>Docker 删除本地镜像</h2><p>如果要删除本地的镜像，可以使用 <code>docker image rm</code> 命令，其格式为：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker image rm [选项] &lt;镜像1&gt; [&lt;镜像2&gt; ...]</span><br><span class="line"><span class="comment">#删除所有容器</span></span><br><span class="line">$ docker container prune</span><br></pre></td></tr></table></figure>

<p><strong>用 ID、镜像名、摘要删除镜像</strong></p>
<p>其中，<code>&lt;镜像&gt;</code> 可以是 <code>镜像短 ID</code>、<code>镜像长 ID</code>、<code>镜像名</code> 或者 <code>镜像摘要</code>。</p>
<p>比如我们有这么一些镜像：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls</span><br><span class="line">REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">centos                      latest              0584b3d2cf6d        3 weeks ago         196.5 MB</span><br><span class="line">redis                       alpine              501ad78535f0        3 weeks ago         21.03 MB</span><br><span class="line">docker                      latest              cf693ec9b5c7        3 weeks ago         105.1 MB</span><br><span class="line">nginx                       latest              e43d811ce2f4        5 weeks ago         181.5 MB</span><br></pre></td></tr></table></figure>

<p>我们可以用镜像的完整 ID，也称为 <code>长 ID</code>，来删除镜像。使用脚本的时候可能会用长 ID，但是人工输入就太累了，所以更多的时候是用 <code>短 ID</code> 来删除镜像。<code>docker image ls</code> 默认列出的就已经是短 ID 了，一般取前3个字符以上，只要足够区分于别的镜像就可以了。</p>
<p>比如这里，如果我们要删除 <code>redis:alpine</code> 镜像，可以执行：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ docker image rm 501</span><br><span class="line">Untagged: redis:alpine</span><br><span class="line">Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d</span><br><span class="line">Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7</span><br><span class="line">Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b</span><br><span class="line">Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23</span><br><span class="line">Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa</span><br><span class="line">Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3</span><br><span class="line">Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7</span><br></pre></td></tr></table></figure>

<p>我们也可以用<code>镜像名</code>，也就是 <code>&lt;仓库名&gt;:&lt;标签&gt;</code>，来删除镜像。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker image rm centos</span><br><span class="line">Untagged: centos:latest</span><br><span class="line">Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c</span><br><span class="line">Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a</span><br><span class="line">Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38</span><br></pre></td></tr></table></figure>

<p>当然，更精确的是使用 <code>镜像摘要</code> 删除镜像。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls --digests</span><br><span class="line">REPOSITORY                  TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE</span><br><span class="line">node                        slim                sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228   6e0c4c8e3913        3 weeks ago         214 MB</span><br><span class="line"></span><br><span class="line">$ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228</span><br><span class="line">Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228</span><br></pre></td></tr></table></figure>

<p><strong>Untagged 和 Deleted</strong></p>
<p>如果观察上面这几个命令的运行输出信息的话，你会注意到删除行为分为两类，一类是 <code>Untagged</code>，另一类是 <code>Deleted</code>。我们之前介绍过，镜像的唯一标识是其 ID 和摘要，而一个镜像可以有多个标签。</p>
<p>因此当我们使用上面命令删除镜像的时候，实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消，这就是我们看到的 <code>Untagged</code> 的信息。因为一个镜像可以对应多个标签，因此当我们删除了所指定的标签后，可能还有别的标签指向了这个镜像，如果是这种情况，那么 <code>Delete</code> 行为就不会发生。所以并非所有的 <code>docker image rm</code> 都会产生删除镜像的行为，有可能仅仅是取消了某个标签而已。</p>
<p>当该镜像所有的标签都被取消了，该镜像很可能会失去了存在的意义，因此会触发删除行为。镜像是多层存储结构，因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变动非常容易，因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况，依旧不会触发删除该层的行为。直到没有任何层依赖当前层时，才会真实的删除当前层。这就是为什么，有时候会奇怪，为什么明明没有别的标签指向这个镜像，但是它还是存在的原因，也是为什么有时候会发现所删除的层数和自己  <code>docker pull</code> 看到的层数不一样的源。</p>
<p>除了镜像依赖以外，还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在（即使容器没有运行），那么同样不可以删除这个镜像。之前讲过，容器是以镜像为基础，再加一层容器存储层，组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的，那么删除必然会导致故障。如果这些容器是不需要的，应该先将它们删除，然后再来删除镜像。</p>
<p><strong>用 docker image ls 命令来配合</strong></p>
<p>像其它可以承接多个实体的命令一样，可以使用 <code>docker image ls -q</code> 来配合使用 <code>docker image rm</code>，这样可以成批的删除希望删除的镜像。我们在“镜像列表”章节介绍过很多过滤镜像列表的方式都可以拿过来使用。</p>
<p>比如，我们需要删除所有仓库名为 <code>redis</code> 的镜像：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker image rm $(docker image ls -q redis)</span><br></pre></td></tr></table></figure>

<p>或者删除所有在 <code>mongo:3.2</code> 之前的镜像：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker image rm $(docker image ls -q -f before=mongo:3.2)</span><br></pre></td></tr></table></figure>

<p>充分利用你的想象力和 Linux 命令行的强大，你可以完成很多非常赞的功能。</p>
<p><strong>CentOS/RHEL 的用户需要注意的事项</strong></p>
<p>在 Ubuntu/Debian 上有 <code>UnionFS</code> 可以使用，如 <code>aufs</code> 或者 <code>overlay2</code>，而 CentOS 和 RHEL 的内核中没有相关驱动。因此对于这类系统，一般使用 <code>devicemapper</code> 驱动利用 LVM 的一些机制来模拟分层存储。这样的做法除了性能比较差外，稳定性一般也不好，而且配置相对复杂。Docker 安装在 CentOS/RHEL 上后，会默认选择 <code>devicemapper</code>，但是为了简化配置，其 <code>devicemapper</code> 是跑在一个稀疏文件模拟的块设备上，也被称为 <code>loop-lvm</code>。这样的选择是因为不需要额外配置就可以运行 Docker，这是自动配置唯一能做到的事情。但是 <code>loop-lvm</code> 的做法非常不好，其稳定性、性能更差，无论是日志还是 <code>docker info</code> 中都会看到警告信息。官方文档有明确的文章讲解了如何配置块设备给 <code>devicemapper</code> 驱动做存储层的做法，这类做法也被称为配置 <code>direct-lvm</code>。</p>
<p>除了前面说到的问题外，<code>devicemapper</code> + <code>loop-lvm</code> 还有一个缺陷，因为它是稀疏文件，所以它会不断增长。用户在使用过程中会注意到 <code>/var/lib/docker/devicemapper/devicemapper/data</code> 不断增长，而且无法控制。很多人会希望删除镜像或者可以解决这个问题，结果发现效果并不明显。原因就是这个稀疏文件的空间释放后基本不进行垃圾回收的问题。因此往往会出现即使删除了文件内容，空间却无法回收，随着使用这个稀疏文件一直在不断增长。</p>
<p>所以对于 CentOS/RHEL 的用户来说，在没有办法使用 <code>UnionFS</code> 的情况下，一定要配置 <code>direct-lvm</code> 给 <code>devicemapper</code>，无论是为了性能、稳定性还是空间利用率。</p>
<p><em>或许有人注意到了 CentOS 7 中存在被 backports 回来的 overlay 驱动，不过 CentOS 里的这个驱动达不到生产环境使用的稳定程度，所以不推荐使用。</em></p>
<h2 id="利用-commit-理解镜像构成"><a href="#利用-commit-理解镜像构成" class="headerlink" title="利用 commit 理解镜像构成"></a>利用 commit 理解镜像构成</h2><p>注意： <code>docker commit</code> 命令除了学习之外，还有一些特殊的应用场合，比如被入侵后保存现场等。但是，不要使用 <code>docker commit</code> 定制镜像，定制镜像应该使用 <code>Dockerfile</code> 来完成。如果你想要定制镜像请查看下一小节。</p>
<p>镜像是容器的基础，每次执行 <code>docker run</code> 的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中，我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求，而当这些镜像无法直接满足需求时，我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。</p>
<p>回顾一下之前我们学到的知识，镜像是多层存储，每一层是在前一层的基础上进行的修改；而容器同样也是多层存储，是在以镜像为基础层，在其基础上加一层作为容器运行时的存储层。</p>
<p>现在让我们以定制一个 Web 服务器为例子，来讲解镜像是如何构建的。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run --name webserver -d -p 80:80 nginx</span><br></pre></td></tr></table></figure>

<p>这条命令会用 <code>nginx</code> 镜像启动一个容器，命名为 <code>webserver</code>，并且映射了 80 端口，这样我们可以用浏览器去访问这个 <code>nginx</code> 服务器。</p>
<p>如果是在 Linux 本机运行的 Docker，或者如果使用的是 Docker for Mac、Docker for Windows，那么可以直接访问：<a href="http://localhost" target="_blank" rel="noopener">http://localhost</a>；如果使用的是 Docker Toolbox，或者是在虚拟机、云服务器上安装的 Docker，则需要将 <code>localhost</code> 换为虚拟机地址或者实际云服务器地址。</p>
<p>直接用浏览器访问的话，我们会看到默认的 Nginx 欢迎页面。</p>
<p><img src="/myblog/2020/03/30/Docker/images-mac-example-nginx.png" alt="img"></p>
<p>现在，假设我们非常不喜欢这个欢迎页面，我们希望改成欢迎 Docker 的文字，我们可以使用 <code>docker exec</code> 命令进入容器，修改其内容。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker <span class="built_in">exec</span> -it webserver bash</span><br><span class="line">root@3729b97e8226:/<span class="comment"># echo '&lt;h1&gt;Hello, Docker!&lt;/h1&gt;' &gt; /usr/share/nginx/html/index.html</span></span><br><span class="line">root@3729b97e8226:/<span class="comment"># exit</span></span><br><span class="line"><span class="built_in">exit</span></span><br></pre></td></tr></table></figure>

<p>我们以交互式终端方式进入 <code>webserver</code> 容器，并执行了 <code>bash</code> 命令，也就是获得一个可操作的 Shell。</p>
<p>然后，我们用 <code>&lt;h1&gt;Hello, Docker!&lt;/h1&gt;</code> 覆盖了 <code>/usr/share/nginx/html/index.html</code> 的内容。</p>
<p>现在我们再刷新浏览器的话，会发现内容被改变了。</p>
<p><img src="/myblog/2020/03/30/Docker/images-create-nginx-docker.png" alt="img">我们修改了容器的文件，也就是改动了容器的存储层。我们可以通过 <code>docker diff</code> 命令看到具体的改动。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">$ docker diff webserver</span><br><span class="line">C /root</span><br><span class="line">A /root/.bash_history</span><br><span class="line">C /run</span><br><span class="line">C /usr</span><br><span class="line">C /usr/share</span><br><span class="line">C /usr/share/nginx</span><br><span class="line">C /usr/share/nginx/html</span><br><span class="line">C /usr/share/nginx/html/index.html</span><br><span class="line">C /var</span><br><span class="line">C /var/cache</span><br><span class="line">C /var/cache/nginx</span><br><span class="line">A /var/cache/nginx/client_temp</span><br><span class="line">A /var/cache/nginx/fastcgi_temp</span><br><span class="line">A /var/cache/nginx/proxy_temp</span><br><span class="line">A /var/cache/nginx/scgi_temp</span><br><span class="line">A /var/cache/nginx/uwsgi_temp</span><br></pre></td></tr></table></figure>

<p>现在我们定制好了变化，我们希望能将其保存下来形成镜像。</p>
<p>要知道，当我们运行一个容器的时候（如果不使用卷的话），我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 <code>docker commit</code> 命令，可以将容器的存储层保存下来成为镜像。换句话说，就是在原有镜像的基础上，再叠加上容器的存储层，并构成新的镜像。以后我们运行这个新镜像的时候，就会拥有原有容器最后的文件变化。</p>
<p><code>docker commit</code> 的语法格式为：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker commit [选项] &lt;容器ID或容器名&gt; [&lt;仓库名&gt;[:&lt;标签&gt;]]</span><br></pre></td></tr></table></figure>

<p>我们可以用下面的命令将容器保存为镜像：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ docker commit \</span><br><span class="line">    --author <span class="string">"Tao Wang &lt;twang2218@gmail.com&gt;"</span> \</span><br><span class="line">    --message <span class="string">"修改了默认网页"</span> \</span><br><span class="line">    webserver \</span><br><span class="line">    nginx:v2</span><br><span class="line">sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214</span><br></pre></td></tr></table></figure>

<p>其中 <code>--author</code> 是指定修改的作者，而 <code>--message</code> 则是记录本次修改的内容。这点和 <code>git</code> 版本控制相似，不过这里这些信息可以省略留空。</p>
<p>我们可以在 <code>docker image ls</code> 中看到这个新定制的镜像：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">$ docker image ls nginx</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">nginx               v2                  07e334659748        9 seconds ago       181.5 MB</span><br><span class="line">nginx               1.11                05a60462f8ba        12 days ago         181.5 MB</span><br><span class="line">nginx               latest              e43d811ce2f4        4 weeks ago         181.5 MB```</span><br><span class="line"></span><br><span class="line">我们还可以用 `docker <span class="built_in">history</span>` 具体查看镜像内的历史记录，如果比较 `nginx:latest` 的历史记录，我们会发现新增了我们刚刚提交的这一层。</span><br><span class="line"></span><br><span class="line">```bash</span><br><span class="line">$ docker <span class="built_in">history</span> nginx:v2</span><br><span class="line">IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT</span><br><span class="line">07e334659748        54 seconds ago      nginx -g daemon off;                            95 B                修改了默认网页</span><br><span class="line">e43d811ce2f4        4 weeks ago         /bin/sh -c <span class="comment">#(nop)  CMD ["nginx" "-g" "daemon    0 B</span></span><br><span class="line">&lt;missing&gt;           4 weeks ago         /bin/sh -c <span class="comment">#(nop)  EXPOSE 443/tcp 80/tcp        0 B</span></span><br><span class="line">&lt;missing&gt;           4 weeks ago         /bin/sh -c ln -sf /dev/stdout /var/<span class="built_in">log</span>/nginx/   22 B</span><br><span class="line">&lt;missing&gt;           4 weeks ago         /bin/sh -c apt-key adv --keyserver hkp://pgp.   58.46 MB</span><br><span class="line">&lt;missing&gt;           4 weeks ago         /bin/sh -c <span class="comment">#(nop)  ENV NGINX_VERSION=1.11.5-1   0 B</span></span><br><span class="line">&lt;missing&gt;           4 weeks ago         /bin/sh -c <span class="comment">#(nop)  MAINTAINER NGINX Docker Ma   0 B</span></span><br><span class="line">&lt;missing&gt;           4 weeks ago         /bin/sh -c <span class="comment">#(nop)  CMD ["/bin/bash"]            0 B</span></span><br><span class="line">&lt;missing&gt;           4 weeks ago         /bin/sh -c <span class="comment">#(nop) ADD file:23aa4f893e3288698c   123 MB</span></span><br></pre></td></tr></table></figure>

<p>新的镜像定制好后，我们可以来运行这个镜像。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --name web2 -d -p 81:80 nginx:v2</span><br></pre></td></tr></table></figure>

<p>这里我们命名为新的服务为 <code>web2</code>，并且映射到 <code>81</code> 端口。如果是 Docker for Mac/Windows 或 Linux 桌面的话，我们就可以直接访问 <a href="http://localhost:81" target="_blank" rel="noopener">http://localhost:81</a><a href="http://localhost:81" target="_blank" rel="noopener"> </a> 看到结果，其内容应该和之前修改后的 <code>webserver</code> 一样。</p>
<p>至此，我们第一次完成了定制镜像，使用的是 <code>docker commit</code> 命令，手动操作给旧的镜像添加了新的一层，形成新的镜像，对镜像多层存储应该有了更直观的感觉。</p>
<p><strong>慎用 `docker commit</strong>`</p>
<p>使用 <code>docker commit</code> 命令虽然可以比较直观的帮助理解镜像分层存储的概念，但是实际环境中并不会这样使用。</p>
<p>首先，如果仔细观察之前的 <code>docker diff webserver</code> 的结果，你会发现除了真正想要修改的 <code>/usr/share/nginx/html/index.html</code> 文件外，由于命令的执行，还有很多文件被改动或添加了。这还仅仅是最简单的操作，如果是安装软件包、编译构建，那会有大量的无关内容被添加进来，如果不小心清理，将会导致镜像极为臃肿。</p>
<p>此外，使用 <code>docker commit</code> 意味着所有对镜像的操作都是黑箱操作，生成的镜像也被称为<strong>黑箱镜像</strong>，换句话说，就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像，别人根本无从得知。而且，即使是这个制作镜像的人，过一段时间后也无法记清具体在操作的。虽然 <code>docker diff</code> 或许可以告诉得到一些线索，但是远远不到可以确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。</p>
<p>而且，回顾之前提及的镜像所使用的分层存储的概念，除当前层外，之前的每一层都是不会发生改变的，换句话说，任何修改的结果仅仅是在当前层进行标记、添加、修改，而不会改动上一层。如果使用 <code>docker commit</code> 制作镜像，以及后期修改的话，每一次修改都会让镜像更加臃肿一次，所删除的上一层的东西并不会丢失，会一直如影随形的跟着这个镜像，即使根本无法访问到。这会让镜像更加臃肿。</p>
<h2 id="使用-Dockerfile-定制镜像"><a href="#使用-Dockerfile-定制镜像" class="headerlink" title="使用 Dockerfile 定制镜像"></a>使用 Dockerfile 定制镜像</h2><h3 id="Dockerfile-定制镜像"><a href="#Dockerfile-定制镜像" class="headerlink" title="Dockerfile 定制镜像"></a>Dockerfile 定制镜像</h3><p>从刚才的 <code>docker commit</code>  的学习中，我们可以了解到，镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本，用这个脚本来构建、定制镜像，那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是  Dockerfile。</p>
<p>Dockerfile 是一个文本文件，其内包含了一条条的<strong>指令(Instruction)</strong>，每一条指令构建一层，因此每一条指令的内容，就是描述该层应当如何构建。</p>
<p>还以之前定制 <code>nginx</code> 镜像为例，这次我们使用 Dockerfile 来定制。</p>
<p>在一个空白目录中，建立一个文本文件，并命名为 <code>Dockerfile</code>：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ mkdir mynginx</span><br><span class="line">$ <span class="built_in">cd</span> mynginx</span><br><span class="line">$ touch Dockerfile</span><br></pre></td></tr></table></figure>

<p>其内容为：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> nginx</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> <span class="built_in">echo</span> <span class="string">'&lt;h1&gt;Hello, Docker!&lt;/h1&gt;'</span> &gt; /usr/share/nginx/html/index.html</span></span><br></pre></td></tr></table></figure>

<p>这个 Dockerfile 很简单，一共就两行。涉及到了两条指令，<code>FROM</code> 和 <code>RUN</code>。</p>
<p><strong>FROM 指定基础镜像</strong></p>
<p>所谓定制镜像，那一定是以一个镜像为基础，在其上进行定制。就像我们之前运行了一个 <code>nginx</code> 镜像的容器，再进行修改一样，基础镜像是必须指定的。而 <code>FROM</code> 就是指定<strong>基础镜像</strong>，因此一个 <code>Dockerfile</code> 中 <code>FROM</code> 是必备的指令，并且必须是第一条指令。</p>
<p>在 <a href="https://store.docker.com" target="_blank" rel="noopener">Docker Store</a><a href="https://store.docker.com" target="_blank" rel="noopener"> </a> 上有非常多的高质量的官方镜像，有可以直接拿来使用的服务类的镜像，如 <a href="https://store.docker.com/images/nginx/" target="_blank" rel="noopener"><code>nginx</code></a><a href="https://store.docker.com/images/nginx/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/redis/" target="_blank" rel="noopener"><code>redis</code></a><a href="https://store.docker.com/images/redis/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/mongo/" target="_blank" rel="noopener"><code>mongo</code></a><a href="https://store.docker.com/images/mongo/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/mysql/" target="_blank" rel="noopener"><code>mysql</code></a><a href="https://store.docker.com/images/mysql/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/httpd/" target="_blank" rel="noopener"><code>httpd</code></a><a href="https://store.docker.com/images/httpd/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/php/" target="_blank" rel="noopener"><code>php</code></a><a href="https://store.docker.com/images/php/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/tomcat/" target="_blank" rel="noopener"><code>tomcat</code></a><a href="https://store.docker.com/images/tomcat/" target="_blank" rel="noopener"> </a> 等；也有一些方便开发、构建、运行各种语言应用的镜像，如 <a href="https://store.docker.com/images/node" target="_blank" rel="noopener"><code>node</code></a><a href="https://store.docker.com/images/node" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/openjdk/" target="_blank" rel="noopener"><code>openjdk</code></a><a href="https://store.docker.com/images/openjdk/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/python/" target="_blank" rel="noopener"><code>python</code></a><a href="https://store.docker.com/images/python/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/ruby/" target="_blank" rel="noopener"><code>ruby</code></a><a href="https://store.docker.com/images/ruby/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/golang/" target="_blank" rel="noopener"><code>golang</code></a><a href="https://store.docker.com/images/golang/" target="_blank" rel="noopener"> </a> 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。</p>
<p>如果没有找到对应服务的镜像，官方镜像中还提供了一些更为基础的操作系统镜像，如 <a href="https://store.docker.com/images/ubuntu/" target="_blank" rel="noopener"><code>ubuntu</code></a><a href="https://store.docker.com/images/ubuntu/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/debian/" target="_blank" rel="noopener"><code>debian</code></a><a href="https://store.docker.com/images/debian/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/centos/" target="_blank" rel="noopener"><code>centos</code></a><a href="https://store.docker.com/images/centos/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/fedora/" target="_blank" rel="noopener"><code>fedora</code></a><a href="https://store.docker.com/images/fedora/" target="_blank" rel="noopener"> </a>、<a href="https://store.docker.com/images/alpine/" target="_blank" rel="noopener"><code>alpine</code></a><a href="https://store.docker.com/images/alpine/" target="_blank" rel="noopener"> </a> 等，这些操作系统的软件库为我们提供了更广阔的扩展空间。</p>
<p>除了选择现有镜像为基础镜像外，Docker 还存在一个特殊的镜像，名为 <code>scratch</code>。这个镜像是虚拟的概念，并不实际存在，它表示一个空白的镜像。</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> scratch</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>如果你以 <code>scratch</code> 为基础镜像的话，意味着你不以任何镜像为基础，接下来所写的指令将作为镜像第一层开始存在。</p>
<p>不以任何系统为基础，直接将可执行文件复制进镜像的做法并不罕见，比如 <a href="https://hub.docker.com/_/swarm/" target="_blank" rel="noopener"><code>swarm</code></a><a href="https://hub.docker.com/_/swarm/" target="_blank" rel="noopener"> </a>、<a href="https://quay.io/repository/coreos/etcd" target="_blank" rel="noopener"><code>coreos/etcd</code></a><a href="https://quay.io/repository/coreos/etcd" target="_blank" rel="noopener"> </a>。对于 Linux 下静态编译的程序来说，并不需要有操作系统提供运行时支持，所需的一切库都已经在可执行文件里了，因此直接 <code>FROM scratch</code> 会让镜像体积更加小巧。使用 <a href="https://golang.org/" target="_blank" rel="noopener">Go 语言</a><a href="https://golang.org/" target="_blank" rel="noopener"> </a> 开发的应用很多会使用这种方式来制作镜像，这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。</p>
<p><strong>RUN 执行命令</strong></p>
<p><code>RUN</code> 指令是用来执行命令行命令的。由于命令行的强大能力，<code>RUN</code> 指令在定制镜像时是最常用的指令之一。其格式有两种：</p>
<ul>
<li><em>shell</em> 格式：<code>RUN &lt;命令&gt;</code>，就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 <code>RUN</code> 指令就是这种格式。</li>
</ul>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="bash"> <span class="built_in">echo</span> <span class="string">'&lt;h1&gt;Hello, Docker!&lt;/h1&gt;'</span> &gt; /usr/share/nginx/html/index.html</span></span><br></pre></td></tr></table></figure>

<ul>
<li><em>exec</em> 格式：<code>RUN [&quot;可执行文件&quot;, &quot;参数1&quot;, &quot;参数2&quot;]</code>，这更像是函数调用中的格式。</li>
</ul>
<p>既然 <code>RUN</code> 就像 Shell 脚本一样可以执行命令，那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢？比如这样：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> debian:jessie</span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apt-get update</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apt-get install -y gcc libc6-dev make</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> wget -O redis.tar.gz <span class="string">"http://download.redis.io/releases/redis-3.2.5.tar.gz"</span></span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> mkdir -p /usr/src/redis</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> make -C /usr/src/redis</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> make -C /usr/src/redis install</span></span><br></pre></td></tr></table></figure>

<p>之前说过，Dockerfile 中每一个指令都会建立一层，<code>RUN</code> 也不例外。每一个 <code>RUN</code> 的行为，就和刚才我们手工建立镜像的过程一样：新建立一层，在其上执行这些命令，执行结束后，<code>commit</code> 这一层的修改，构成新的镜像。</p>
<p>而上面的这种写法，创建了 7 层镜像。这是完全没有意义的，而且很多运行时不需要的东西，都被装进了镜像里，比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像，不仅仅增加了构建部署的时间，也很容易出错。 这是很多初学 Docker 的人常犯的一个错误。</p>
<p><em>Union FS 是有最大层数限制的，比如 AUFS，曾经是最大不得超过 42 层，现在是不得超过 127 层。</em></p>
<p>上面的 <code>Dockerfile</code> 正确的写法应该是这样：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> debian:jessie</span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> buildDeps=<span class="string">'gcc libc6-dev make'</span> \</span></span><br><span class="line"><span class="bash">    &amp;&amp; apt-get update \</span></span><br><span class="line"><span class="bash">    &amp;&amp; apt-get install -y <span class="variable">$buildDeps</span> \</span></span><br><span class="line"><span class="bash">    &amp;&amp; wget -O redis.tar.gz <span class="string">"http://download.redis.io/releases/redis-3.2.5.tar.gz"</span> \</span></span><br><span class="line"><span class="bash">    &amp;&amp; mkdir -p /usr/src/redis \</span></span><br><span class="line"><span class="bash">    &amp;&amp; tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \</span></span><br><span class="line"><span class="bash">    &amp;&amp; make -C /usr/src/redis \</span></span><br><span class="line"><span class="bash">    &amp;&amp; make -C /usr/src/redis install \</span></span><br><span class="line"><span class="bash">    &amp;&amp; rm -rf /var/lib/apt/lists/* \</span></span><br><span class="line"><span class="bash">    &amp;&amp; rm redis.tar.gz \</span></span><br><span class="line"><span class="bash">    &amp;&amp; rm -r /usr/src/redis \</span></span><br><span class="line"><span class="bash">    &amp;&amp; apt-get purge -y --auto-remove <span class="variable">$buildDeps</span></span></span><br></pre></td></tr></table></figure>



<p>首先，之前所有的命令只有一个目的，就是编译、安装 redis 可执行文件。因此没有必要建立很多层，这只是一层的事情。因此，这里没有使用很多个 <code>RUN</code> 对一一对应不同的命令，而是仅仅使用一个 <code>RUN</code> 指令，并使用 <code>&amp;&amp;</code> 将各个所需命令串联起来。将之前的 7 层，简化为了 1 层。在撰写 Dockerfile 的时候，要经常提醒自己，这并不是在写 Shell 脚本，而是在定义每一层该如何构建。</p>
<p>并且，这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 <code>\</code> 的命令换行方式，以及行首 <code>#</code> 进行注释的格式。良好的格式，比如换行、缩进、注释等，会让维护、排障更为容易，这是一个比较好的习惯。</p>
<p>此外，还可以看到这一组命令的最后添加了清理工作的命令，删除了为了编译构建所需要的软件，清理了所有下载、展开的文件，并且还清理了 <code>apt</code> 缓存文件。这是很重要的一步，我们之前说过，镜像是多层存储，每一层的东西并不会在下一层被删除，会一直跟随着镜像。因此镜像构建时，一定要确保每一层只添加真正需要添加的东西，任何无关的东西都应该清理掉。</p>
<p>很多人初学 Docker 制作出了很臃肿的镜像的原因之一，就是忘记了每一层构建的最后一定要清理掉无关文件。</p>
<p><strong>构建镜像</strong></p>
<p>好了，让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 Dockerfile 的内容，那么让我们来构建这个镜像吧。</p>
<p>在 <code>Dockerfile</code> 文件所在目录执行：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ docker build -t nginx:v3 .</span><br><span class="line">Sending build context to Docker daemon 2.048 kB</span><br><span class="line">Step 1 : FROM nginx</span><br><span class="line"> ---&gt; e43d811ce2f4</span><br><span class="line">Step 2 : RUN <span class="built_in">echo</span> <span class="string">'&lt;h1&gt;Hello, Docker!&lt;/h1&gt;'</span> &gt; /usr/share/nginx/html/index.html</span><br><span class="line"> ---&gt; Running <span class="keyword">in</span> 9cdc27646c7b</span><br><span class="line"> ---&gt; 44aa4490ce2c</span><br><span class="line">Removing intermediate container 9cdc27646c7b</span><br><span class="line">Successfully built 44aa4490ce2c</span><br></pre></td></tr></table></figure>

<p>从命令的输出结果中，我们可以清晰的看到镜像的构建过程。在 <code>Step 2</code> 中，如同我们之前所说的那样，<code>RUN</code> 指令启动了一个容器 <code>9cdc27646c7b</code>，执行了所要求的命令，并最后提交了这一层 <code>44aa4490ce2c</code>，随后删除了所用到的这个容器 <code>9cdc27646c7b</code>。</p>
<p>这里我们使用了 <code>docker build</code> 命令进行镜像构建。其格式为：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build [选项] &lt;上下文路径/URL/-&gt;</span><br></pre></td></tr></table></figure>

<p>在这里我们指定了最终镜像的名称 <code>-t nginx:v3</code>，构建成功后，我们可以像之前运行 <code>nginx:v2</code> 那样来运行这个镜像，其结果会和 <code>nginx:v2</code> 一样。</p>
<p><strong>镜像构建上下文（Context）</strong></p>
<p>如果注意，会看到 <code>docker build</code> 命令最后有一个 <code>.</code>。<code>.</code> 表示当前目录，而 <code>Dockerfile</code> 就在当前目录，因此不少初学者以为这个路径是在指定 <code>Dockerfile</code> 所在路径，这么理解其实是不准确的。如果对应上面的命令格式，你可能会发现，这是在指定<strong>上下文路径</strong>。那么什么是上下文呢？</p>
<p>首先我们要理解 <code>docker build</code> 的工作原理。Docker 在运行时分为 Docker 引擎（也就是服务端守护进程）和客户端工具。Docker 的引擎提供了一组 REST API，被称为 <a href="https://docs.docker.com/engine/reference/api/docker_remote_api/" target="_blank" rel="noopener">Docker Remote API</a>，而如 <code>docker</code> 命令这样的客户端工具，则是通过这组 API 与 Docker 引擎交互，从而完成各种功能。因此，虽然表面上我们好像是在本机执行各种 <code>docker</code> 功能，但实际上，一切都是使用的远程调用形式在服务端（Docker 引擎）完成。也因为这种 C/S 设计，让我们操作远程服务器的 Docker 引擎变得轻而易举。</p>
<p>当我们进行镜像构建的时候，并非所有定制都会通过 <code>RUN</code> 指令完成，经常会需要将一些本地文件复制进镜像，比如通过 <code>COPY</code> 指令、<code>ADD</code> 指令等。而 <code>docker build</code> 命令构建镜像，其实并非在本地构建，而是在服务端，也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中，如何才能让服务端获得本地文件呢？</p>
<p>这就引入了上下文的概念。当构建的时候，用户会指定构建镜像上下文的路径，<code>docker build</code> 命令得知这个路径后，会将路径下的所有内容打包，然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后，展开就会获得构建镜像所需的一切文件。</p>
<p>如果在 <code>Dockerfile</code> 中这么写：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">COPY</span><span class="bash"> ./package.json /app/</span></span><br></pre></td></tr></table></figure>

<p>这并不是要复制执行 <code>docker build</code> 命令所在的目录下的 <code>package.json</code>，也不是复制 <code>Dockerfile</code> 所在目录下的 <code>package.json</code>，而是复制 <strong>上下文（context）</strong> 目录下的 <code>package.json</code>。</p>
<p>因此，<code>COPY</code> 这类指令中的源文件的路径都是<em>相对路径</em>。这也是初学者经常会问的为什么 <code>COPY ../package.json /app</code> 或者 <code>COPY /opt/xxxx /app</code> 无法工作的原因，因为这些路径已经超出了上下文的范围，Docker 引擎无法获得这些位置的文件。如果真的需要那些文件，应该将它们复制到上下文目录中去。</p>
<p>现在就可以理解刚才的命令 <code>docker build -t nginx:v3 .</code> 中的这个 <code>.</code>，实际上是在指定上下文的目录，<code>docker build</code> 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。</p>
<p>如果观察 <code>docker build</code> 输出，我们其实已经看到了这个发送上下文的过程：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker build -t nginx:v3 .</span><br><span class="line">Sending build context to Docker daemon 2.048 kB</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>理解构建上下文对于镜像构建是很重要的，避免犯一些不应该的错误。比如有些初学者在发现 <code>COPY /opt/xxxx /app</code> 不工作后，于是干脆将 <code>Dockerfile</code> 放到了硬盘根目录去构建，结果发现 <code>docker build</code> 执行后，在发送一个几十 GB 的东西，极为缓慢而且很容易构建失败。那是因为这种做法是在让 <code>docker build</code> 打包整个硬盘，这显然是使用错误。</p>
<p>一般来说，应该会将 <code>Dockerfile</code> 置于一个空目录下，或者项目根目录下。如果该目录下没有所需文件，那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎，那么可以用 <code>.gitignore</code> 一样的语法写一个 <code>.dockerignore</code>，该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。</p>
<p>那么为什么会有人误以为 <code>.</code> 是指定 <code>Dockerfile</code> 所在目录呢？这是因为在默认情况下，如果不额外指定 <code>Dockerfile</code> 的话，会将上下文目录下的名为 <code>Dockerfile</code> 的文件作为 Dockerfile。</p>
<p>这只是默认行为，实际上 <code>Dockerfile</code> 的文件名并不要求必须为 <code>Dockerfile</code>，而且并不要求必须位于上下文目录中，比如可以用 <code>-f ../Dockerfile.php</code> 参数指定某个文件作为 <code>Dockerfile</code>。</p>
<p>当然，一般大家习惯性的会使用默认的文件名 <code>Dockerfile</code>，以及会将其置于镜像构建上下文目录中。</p>
<h3 id="其它-docker-build-的用法"><a href="#其它-docker-build-的用法" class="headerlink" title="其它 docker build 的用法"></a>其它 <code>docker build</code> 的用法</h3><p><strong>直接用 Git repo 进行构建</strong></p>
<p>或许你已经注意到了，<code>docker build</code> 还支持从 URL 构建，比如可以直接从 Git repo 中构建：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ docker build https://github.com/twang2218/gitlab-ce-zh.git<span class="comment">#:8.14</span></span><br><span class="line">docker build https://github.com/twang2218/gitlab-ce-zh.git\<span class="comment">#:8.14</span></span><br><span class="line">Sending build context to Docker daemon 2.048 kB</span><br><span class="line">Step 1 : FROM gitlab/gitlab-ce:8.14.0-ce.0</span><br><span class="line">8.14.0-ce.0: Pulling from gitlab/gitlab-ce</span><br><span class="line">aed15891ba52: Already exists</span><br><span class="line">773ae8583d14: Already exists</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>这行命令指定了构建所需的 Git repo，并且指定默认的 <code>master</code> 分支，构建目录为 <code>/8.14/</code>，然后 Docker 就会自己去 <code>git clone</code> 这个项目、切换到指定分支、并进入到指定目录后开始构建。</p>
<p><strong>用给定的 tar 压缩包构建</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build http://server/context.tar.gz</span><br></pre></td></tr></table></figure>

<p>如果所给出的 URL 不是个 Git repo，而是个 <code>tar</code> 压缩包，那么 Docker 引擎会下载这个包，并自动解压缩，以其作为上下文，开始构建。</p>
<p><strong>从标准输入中读取 Dockerfile 进行构建</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build - &lt; Dockerfile</span><br></pre></td></tr></table></figure>

<p>或</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat Dockerfile | docker build -</span><br></pre></td></tr></table></figure>

<p>如果标准输入传入的是文本文件，则将其视为 <code>Dockerfile</code>，并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容，它没有上下文，因此不可以像其他方法那样可以将本地文件 <code>COPY</code> 进镜像之类的事情。</p>
<p><strong>从标准输入中读取上下文压缩包进行构建</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build - &lt; context.tar.gz</span><br></pre></td></tr></table></figure>

<p>如果发现标准输入的文件格式是 <code>gzip</code>、<code>bzip2</code> 以及 <code>xz</code> 的话，将会使其为上下文压缩包，直接将其展开，将里面视为上下文，并开始构建。</p>
<h3 id="Dockerfile-指令详解"><a href="#Dockerfile-指令详解" class="headerlink" title="Dockerfile 指令详解"></a>Dockerfile 指令详解</h3><p>我们已经介绍了 <code>FROM</code>，<code>RUN</code>，还提及了 <code>COPY</code>, <code>ADD</code>，其实 <code>Dockerfile</code> 功能很强大，它提供了十多个指令。下面我们继续讲解其他的指令。</p>
<p><strong>COPY 复制文件</strong></p>
<p>格式：</p>
<ul>
<li><code>COPY &lt;源路径&gt;... &lt;目标路径&gt;</code></li>
<li><code>COPY [&quot;&lt;源路径1&gt;&quot;,... &quot;&lt;目标路径&gt;&quot;]</code></li>
</ul>
<p>和 <code>RUN</code> 指令一样，也有两种格式，一种类似于命令行，一种类似于函数调用。</p>
<p><code>COPY</code> 指令将从构建上下文目录中 <code>&lt;源路径&gt;</code> 的文件/目录复制到新的一层的镜像内的 <code>&lt;目标路径&gt;</code> 位置。比如：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">COPY</span><span class="bash"> package.json /usr/src/app/</span></span><br></pre></td></tr></table></figure>

<p><code>&lt;源路径&gt;</code> 可以是多个，甚至可以是通配符，其通配符规则要满足 Go 的 <a href="https://golang.org/pkg/path/filepath/#Match" target="_blank" rel="noopener"><code>filepath.Match</code></a><a href="https://golang.org/pkg/path/filepath/#Match" target="_blank" rel="noopener"> </a> 规则，如：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">COPY</span><span class="bash"> hom* /mydir/</span></span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> hom?.txt /mydir/</span></span><br></pre></td></tr></table></figure>

<p><code>&lt;目标路径&gt;</code> 可以是容器内的绝对路径，也可以是相对于工作目录的相对路径（工作目录可以用 <code>WORKDIR</code> 指令来指定）。目标路径不需要事先创建，如果目录不存在会在复制文件前先行创建缺失目录。</p>
<p>此外，还需要注意一点，使用 <code>COPY</code> 指令，源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。</p>
<p><strong>ADD 更高级的复制文件</strong></p>
<p><code>ADD</code> 指令和 <code>COPY</code> 的格式和性质基本一致。但是在 <code>COPY</code> 基础上增加了一些功能。</p>
<p>比如 <code>&lt;源路径&gt;</code> 可以是一个 <code>URL</code>，这种情况下，Docker 引擎会试图去下载这个链接的文件放到 <code>&lt;目标路径&gt;</code> 去。下载后的文件权限自动设置为 <code>600</code>，如果这并不是想要的权限，那么还需要增加额外的一层 <code>RUN</code> 进行权限调整，另外，如果下载的是个压缩包，需要解压缩，也一样还需要额外的一层 <code>RUN</code> 指令进行解压缩。所以不如直接使用 <code>RUN</code> 指令，然后使用 <code>wget</code> 或者 <code>curl</code> 工具下载，处理权限、解压缩、然后清理无用文件更合理。因此，这个功能其实并不实用，而且不推荐使用。</p>
<p>如果 <code>&lt;源路径&gt;</code> 为一个 <code>tar</code> 压缩文件的话，压缩格式为 <code>gzip</code>, <code>bzip2</code> 以及 <code>xz</code> 的情况下，<code>ADD</code> 指令将会自动解压缩这个压缩文件到 <code>&lt;目标路径&gt;</code> 去。</p>
<p>在某些情况下，这个自动解压缩的功能非常有用，比如官方镜像 <code>ubuntu</code> 中：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> scratch</span><br><span class="line"><span class="keyword">ADD</span><span class="bash"> ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>但在某些情况下，如果我们真的是希望复制个压缩文件进去，而不解压缩，这时就不可以使用 <code>ADD</code> 命令了。</p>
<p>在 Docker 官方的 <code>Dockerfile 最佳实践文档</code> 中要求，尽可能的使用 <code>COPY</code>，因为 <code>COPY</code> 的语义很明确，就是复制文件而已，而 <code>ADD</code> 则包含了更复杂的功能，其行为也不一定很清晰。最适合使用 <code>ADD</code> 的场合，就是所提及的需要自动解压缩的场合。</p>
<p>另外需要注意的是，<code>ADD</code> 指令会令镜像构建缓存失效，从而可能会令镜像构建变得比较缓慢。</p>
<p>因此在 <code>COPY</code> 和 <code>ADD</code> 指令中选择的时候，可以遵循这样的原则，所有的文件复制均使用 <code>COPY</code> 指令，仅在需要自动解压缩的场合使用 <code>ADD</code>。</p>
<p><strong>CMD 容器启动命令</strong></p>
<p><code>CMD</code> 指令的格式和 <code>RUN</code> 相似，也是两种格式：</p>
<ul>
<li><code>shell</code> 格式：<code>CMD &lt;命令&gt;</code></li>
<li><code>exec</code> 格式：<code>CMD [&quot;可执行文件&quot;, &quot;参数1&quot;, &quot;参数2&quot;...]</code></li>
<li>参数列表格式：<code>CMD [&quot;参数1&quot;, &quot;参数2&quot;...]</code>。在指定了 <code>ENTRYPOINT</code> 指令后，用 <code>CMD</code> 指定具体的参数。</li>
</ul>
<p>之前介绍容器的时候曾经说过，Docker 不是虚拟机，容器就是进程。既然是进程，那么在启动容器的时候，需要指定所运行的程序及参数。<code>CMD</code> 指令就是用于指定默认的容器主进程的启动命令的。</p>
<p>在运行时可以指定新的命令来替代镜像设置中的这个默认命令，比如，<code>ubuntu</code> 镜像默认的 <code>CMD</code> 是 <code>/bin/bash</code>，如果我们直接 <code>docker run -it ubuntu</code> 的话，会直接进入 <code>bash</code>。我们也可以在运行时指定运行别的命令，如 <code>docker run -it ubuntu cat /etc/os-release</code>。这就是用 <code>cat /etc/os-release</code> 命令替换了默认的 <code>/bin/bash</code> 命令了，输出了系统版本信息。</p>
<p>在指令格式上，一般推荐使用 <code>exec</code> 格式，这类格式在解析时会被解析为 JSON 数组，因此一定要使用双引号 <code>&quot;</code>，而不要使用单引号。</p>
<p>如果使用 <code>shell</code> 格式的话，实际的命令会被包装为 <code>sh -c</code> 的参数的形式进行执行。比如：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CMD</span><span class="bash"> <span class="built_in">echo</span> <span class="variable">$HOME</span></span></span><br></pre></td></tr></table></figure>

<p>在实际执行中，会将其变更为：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CMD</span><span class="bash"> [ <span class="string">"sh"</span>, <span class="string">"-c"</span>, <span class="string">"echo <span class="variable">$HOME</span>"</span> ]</span></span><br></pre></td></tr></table></figure>

<p>这就是为什么我们可以使用环境变量的原因，因为这些环境变量会被 shell 进行解析处理。</p>
<p>提到 <code>CMD</code> 就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。</p>
<p>Docker 不是虚拟机，容器中的应用都应该以前台执行，而不是像虚拟机、物理机里面那样，用 upstart/systemd 去启动后台服务，容器内没有后台服务的概念。</p>
<p>一些初学者将 <code>CMD</code> 写为：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CMD</span><span class="bash"> service nginx start</span></span><br></pre></td></tr></table></figure>

<p>然后发现容器执行后就立即退出了。甚至在容器内去使用 <code>systemctl</code> 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念，没有区分容器和虚拟机的差异，依旧在以传统虚拟机的角度去理解容器。</p>
<p>对于容器而言，其启动程序就是容器应用进程，容器就是为了主进程而存在的，主进程退出，容器就失去了存在的意义，从而退出，其它辅助进程不是它需要关心的东西。</p>
<p>而使用 <code>service nginx start</code> 命令，则是希望 upstart 来以后台守护进程形式启动 <code>nginx</code> 服务。而刚才说了 <code>CMD service nginx start</code> 会被理解为 <code>CMD [ &quot;sh&quot;, &quot;-c&quot;, &quot;service nginx start&quot;]</code>，因此主进程实际上是 <code>sh</code>。那么当 <code>service nginx start</code> 命令结束后，<code>sh</code> 也就结束了，<code>sh</code> 作为主进程退出了，自然就会令容器退出。</p>
<p>正确的做法是直接执行 <code>nginx</code> 可执行文件，并且要求以前台形式运行。比如：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CMD</span><span class="bash"> [<span class="string">"nginx"</span>, <span class="string">"-g"</span>, <span class="string">"daemon off;"</span>]</span></span><br></pre></td></tr></table></figure>

<p><strong>ENTRYPOINT 入口点</strong></p>
<p><code>ENTRYPOINT</code> 的格式和 <code>RUN</code> 指令格式一样，分为 <code>exec</code> 格式和 <code>shell</code> 格式。</p>
<p><code>ENTRYPOINT</code> 的目的和 <code>CMD</code> 一样，都是在指定容器启动程序及参数。<code>ENTRYPOINT</code> 在运行时也可以替代，不过比 <code>CMD</code> 要略显繁琐，需要通过 <code>docker run</code> 的参数 <code>--entrypoint</code> 来指定。</p>
<p>当指定了 <code>ENTRYPOINT</code> 后，<code>CMD</code> 的含义就发生了改变，不再是直接的运行其命令，而是将 <code>CMD</code> 的内容作为参数传给 <code>ENTRYPOINT</code> 指令，换句话说实际执行时，将变为：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;ENTRYPOINT&gt; <span class="string">"&lt;CMD&gt;"</span></span><br></pre></td></tr></table></figure>

<p>那么有了 <code>CMD</code> 后，为什么还要有 <code>ENTRYPOINT</code> 呢？这种 <code>&lt;ENTRYPOINT&gt; &quot;&lt;CMD&gt;&quot;</code> 有什么好处么？让我们来看几个场景。</p>
<p><strong>场景一：让镜像变成像命令一样使用</strong></p>
<p>假设我们需要一个得知自己当前公网 IP 的镜像，那么可以先用 <code>CMD</code> 来实现：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> ubuntu:<span class="number">16.04</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apt-get update \</span></span><br><span class="line"><span class="bash">    &amp;&amp; apt-get install -y curl \</span></span><br><span class="line"><span class="bash">    &amp;&amp; rm -rf /var/lib/apt/lists/*</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [ <span class="string">"curl"</span>, <span class="string">"-s"</span>, <span class="string">"http://ip.cn"</span> ]</span></span><br></pre></td></tr></table></figure>

<p>假如我们使用 <code>docker build -t myip .</code> 来构建镜像的话，如果我们需要查询当前公网 IP，只需要执行：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run myip</span><br><span class="line">当前 IP：61.148.226.66 来自：北京市 联通</span><br></pre></td></tr></table></figure>

<p>嗯，这么看起来好像可以直接把镜像当做命令使用了，不过命令总有参数，如果我们希望加参数呢？比如从上面的 <code>CMD</code> 中可以看到实质的命令是 <code>curl</code>，那么如果我们希望显示 HTTP 头信息，就需要加上 <code>-i</code> 参数。那么我们可以直接加 <code>-i</code> 参数给 <code>docker run myip</code> 么？</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run myip -i</span><br><span class="line">docker: Error response from daemon: invalid header field value <span class="string">"oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in <span class="variable">$PATH</span>\"\n"</span>.</span><br></pre></td></tr></table></figure>

<p>我们可以看到可执行文件找不到的报错，<code>executable file not found</code>。之前我们说过，跟在镜像名后面的是 <code>command</code>，运行时会替换 <code>CMD</code> 的默认值。因此这里的 <code>-i</code> 替换了原来的 <code>CMD</code>，而不是添加在原来的 <code>curl -s http://ip.cn</code> 后面。而 <code>-i</code> 根本不是命令，所以自然找不到。</p>
<p>那么如果我们希望加入 <code>-i</code> 这参数，我们就必须重新完整的输入这个命令：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run myip curl -s http://ip.cn -i</span><br></pre></td></tr></table></figure>

<p>这显然不是很好的解决方案，而使用 <code>ENTRYPOINT</code> 就可以解决这个问题。现在我们重新用 <code>ENTRYPOINT</code> 来实现这个镜像：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> ubuntu:<span class="number">16.04</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apt-get update \</span></span><br><span class="line"><span class="bash">    &amp;&amp; apt-get install -y curl \</span></span><br><span class="line"><span class="bash">    &amp;&amp; rm -rf /var/lib/apt/lists/*</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="bash"> [ <span class="string">"curl"</span>, <span class="string">"-s"</span>, <span class="string">"http://ip.cn"</span> ]</span></span><br></pre></td></tr></table></figure>

<p>这次我们再来尝试直接使用 <code>docker run myip -i</code>：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">$ docker run myip</span><br><span class="line">当前 IP：61.148.226.66 来自：北京市 联通</span><br><span class="line"></span><br><span class="line">$ docker run myip -i</span><br><span class="line">HTTP/1.1 200 OK</span><br><span class="line">Server: nginx/1.8.0</span><br><span class="line">Date: Tue, 22 Nov 2016 05:12:40 GMT</span><br><span class="line">Content-Type: text/html; charset=UTF-8</span><br><span class="line">Vary: Accept-Encoding</span><br><span class="line">X-Powered-By: PHP/5.6.24-1~dotdeb+7.1</span><br><span class="line">X-Cache: MISS from cache-2</span><br><span class="line">X-Cache-Lookup: MISS from cache-2:80</span><br><span class="line">X-Cache: MISS from proxy-2_6</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line">Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006</span><br><span class="line">Connection: keep-alive</span><br><span class="line"></span><br><span class="line">当前 IP：61.148.226.66 来自：北京市 联通</span><br></pre></td></tr></table></figure>

<p>可以看到，这次成功了。这是因为当存在 <code>ENTRYPOINT</code> 后，<code>CMD</code> 的内容将会作为参数传给 <code>ENTRYPOINT</code>，而这里 <code>-i</code> 就是新的 <code>CMD</code>，因此会作为参数传给 <code>curl</code>，从而达到了我们预期的效果。</p>
<p><strong>场景二：应用运行前的准备工作</strong></p>
<p>启动容器就是启动主进程，但有些时候，启动主进程前，需要一些准备工作。</p>
<p>比如 <code>mysql</code> 类的数据库，可能需要一些数据库配置、初始化的工作，这些工作要在最终的 mysql 服务器运行之前解决。</p>
<p>此外，可能希望避免使用 <code>root</code> 用户去启动服务，从而提高安全性，而在启动服务前还需要以 <code>root</code> 身份执行一些必要的准备工作，最后切换到服务用户身份启动服务。或者除了服务外，其它命令依旧可以使用 <code>root</code> 身份执行，方便调试等。</p>
<p>这些准备工作是和容器 <code>CMD</code> 无关的，无论 <code>CMD</code> 为什么，都需要事先进行一个预处理的工作。这种情况下，可以写一个脚本，然后放入 <code>ENTRYPOINT</code> 中去执行，而这个脚本会将接到的参数（也就是 <code>&lt;CMD&gt;</code>）作为命令，在脚本最后执行。比如官方镜像 <code>redis</code> 中就是这么做的：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> alpine:<span class="number">3.4</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> addgroup -S redis &amp;&amp; adduser -S -G redis redis</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="bash"> [<span class="string">"docker-entrypoint.sh"</span>]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">6379</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [ <span class="string">"redis-server"</span> ]</span></span><br></pre></td></tr></table></figure>

<p>可以看到其中为了 redis 服务创建了 redis 用户，并在最后指定了 <code>ENTRYPOINT</code> 为 <code>docker-entrypoint.sh</code> 脚本。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line">...</span><br><span class="line"><span class="comment"># allow the container to be started with `--user`</span></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">"<span class="variable">$1</span>"</span> = <span class="string">'redis-server'</span> -a <span class="string">"<span class="variable">$(id -u)</span>"</span> = <span class="string">'0'</span> ]; <span class="keyword">then</span></span><br><span class="line">	chown -R redis .</span><br><span class="line">	<span class="built_in">exec</span> su-exec redis <span class="string">"<span class="variable">$0</span>"</span> <span class="string">"<span class="variable">$@</span>"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">exec</span> <span class="string">"<span class="variable">$@</span>"</span></span><br></pre></td></tr></table></figure>

<p>该脚本的内容就是根据 <code>CMD</code> 的内容来判断，如果是 <code>redis-server</code> 的话，则切换到 <code>redis</code> 用户身份启动服务器，否则依旧使用 <code>root</code> 身份执行。比如：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -it redis id</span><br><span class="line">uid=0(root) gid=0(root) groups=0(root)</span><br></pre></td></tr></table></figure>

<p><strong>ENV 设置环境变量</strong></p>
<p>格式有两种：</p>
<ul>
<li><code>ENV &lt;key&gt; &lt;value&gt;</code></li>
<li><code>ENV &lt;key1&gt;=&lt;value1&gt; &lt;key2&gt;=&lt;value2&gt;...</code></li>
</ul>
<p>这个指令很简单，就是设置环境变量而已，无论是后面的其它指令，如 <code>RUN</code>，还是运行时的应用，都可以直接使用这里定义的环境变量。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ENV VERSION&#x3D;1.0 DEBUG&#x3D;on \</span><br><span class="line">    NAME&#x3D;&quot;Happy Feet&quot;</span><br></pre></td></tr></table></figure>

<p>这个例子中演示了如何换行，以及对含有空格的值用双引号括起来的办法，这和 Shell 下的行为是一致的。</p>
<p>定义了环境变量，那么在后续的指令中，就可以使用这个环境变量。比如在官方 <code>node</code> 镜像 <code>Dockerfile</code> 中，就有类似这样的代码：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ENV</span> NODE_VERSION <span class="number">7.2</span>.<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> curl -SLO <span class="string">"https://nodejs.org/dist/v<span class="variable">$NODE_VERSION</span>/node-v<span class="variable">$NODE_VERSION</span>-linux-x64.tar.xz"</span> \</span></span><br><span class="line"><span class="bash">  &amp;&amp; curl -SLO <span class="string">"https://nodejs.org/dist/v<span class="variable">$NODE_VERSION</span>/SHASUMS256.txt.asc"</span> \</span></span><br><span class="line"><span class="bash">  &amp;&amp; gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \</span></span><br><span class="line"><span class="bash">  &amp;&amp; grep <span class="string">" node-v<span class="variable">$NODE_VERSION</span>-linux-x64.tar.xz\$"</span> SHASUMS256.txt | sha256sum -c - \</span></span><br><span class="line"><span class="bash">  &amp;&amp; tar -xJf <span class="string">"node-v<span class="variable">$NODE_VERSION</span>-linux-x64.tar.xz"</span> -C /usr/<span class="built_in">local</span> --strip-components=1 \</span></span><br><span class="line"><span class="bash">  &amp;&amp; rm <span class="string">"node-v<span class="variable">$NODE_VERSION</span>-linux-x64.tar.xz"</span> SHASUMS256.txt.asc SHASUMS256.txt \</span></span><br><span class="line"><span class="bash">  &amp;&amp; ln -s /usr/<span class="built_in">local</span>/bin/node /usr/<span class="built_in">local</span>/bin/nodejs</span></span><br></pre></td></tr></table></figure>

<p>在这里先定义了环境变量 <code>NODE_VERSION</code>，其后的 <code>RUN</code> 这层里，多次使用 <code>$NODE_VERSION</code> 来进行操作定制。可以看到，将来升级镜像构建版本的时候，只需要更新 <code>7.2.0</code> 即可，<code>Dockerfile</code> 构建维护变得更轻松了。</p>
<p>下列指令可以支持环境变量展开： <code>ADD</code>、<code>COPY</code>、<code>ENV</code>、<code>EXPOSE</code>、<code>LABEL</code>、<code>USER</code>、<code>WORKDIR</code>、<code>VOLUME</code>、<code>STOPSIGNAL</code>、<code>ONBUILD</code>。</p>
<p>可以从这个指令列表里感觉到，环境变量可以使用的地方很多，很强大。通过环境变量，我们可以让一份 <code>Dockerfile</code> 制作更多的镜像，只需使用不同的环境变量即可。</p>
<p><strong>ARG 构建参数</strong></p>
<p>格式：<code>ARG &lt;参数名&gt;[=&lt;默认值&gt;]</code></p>
<p>构建参数和 <code>ENV</code> 的效果一样，都是设置环境变量。所不同的是，<code>ARG</code> 所设置的构建环境的环境变量，在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 <code>ARG</code> 保存密码之类的信息，因为 <code>docker history</code> 还是可以看到所有值的。</p>
<p><code>Dockerfile</code> 中的 <code>ARG</code> 指令是定义参数名称，以及定义其默认值。该默认值可以在构建命令 <code>docker build</code> 中用 <code>--build-arg &lt;参数名&gt;=&lt;值&gt;</code> 来覆盖。</p>
<p>在 1.13 之前的版本，要求 <code>--build-arg</code> 中的参数名，必须在 <code>Dockerfile</code> 中用 <code>ARG</code> 定义过了，换句话说，就是 <code>--build-arg</code> 指定的参数，必须在 <code>Dockerfile</code> 中使用了。如果对应参数没有被使用，则会报错退出构建。从 1.13 开始，这种严格的限制被放开，不再报错退出，而是显示警告信息，并继续构建。这对于使用 CI 系统，用同样的构建流程构建不同的 <code>Dockerfile</code> 的时候比较有帮助，避免构建命令必须根据每个 Dockerfile 的内容修改。</p>
<p><strong>VOLUME 定义匿名卷</strong></p>
<p>格式为：</p>
<ul>
<li><code>VOLUME [&quot;&lt;路径1&gt;&quot;, &quot;&lt;路径2&gt;&quot;...]</code></li>
<li><code>VOLUME &lt;路径&gt;</code></li>
</ul>
<p>之前我们说过，容器运行时应该尽量保持容器存储层不发生写操作，对于数据库类需要保存动态数据的应用，其数据库文件应该保存于卷(volume)中，后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷，在 <code>Dockerfile</code> 中，我们可以事先指定某些目录挂载为匿名卷，这样在运行时如果用户不指定挂载，其应用也可以正常运行，不会向容器存储层写入大量数据。</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">VOLUME</span><span class="bash"> /data</span></span><br></pre></td></tr></table></figure>

<p>这里的 <code>/data</code> 目录就会在运行时自动挂载为匿名卷，任何向 <code>/data</code> 中写入的信息都不会记录进容器存储层，从而保证了容器存储层的无状态化。当然，运行时可以覆盖这个挂载设置。比如：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d -v mydata:/data xxxx</span><br></pre></td></tr></table></figure>

<p>在这行命令中，就使用了 <code>mydata</code> 这个命名卷挂载到了 <code>/data</code> 这个位置，替代了 <code>Dockerfile</code> 中定义的匿名卷的挂载配置。</p>
<p><strong>EXPOSE 暴露端口</strong></p>
<p>格式为 <code>EXPOSE &lt;端口1&gt; [&lt;端口2&gt;...]</code>。</p>
<p><code>EXPOSE</code>  指令是声明运行时容器提供服务端口，这只是一个声明，在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile  中写入这样的声明有两个好处，一个是帮助镜像使用者理解这个镜像服务的守护端口，以方便配置映射；另一个用处则是在运行时使用随机端口映射时，也就是 <code>docker run -P</code> 时，会自动随机映射 <code>EXPOSE</code> 的端口。</p>
<p>此外，在早期 Docker 版本中还有一个特殊的用处。以前所有容器都运行于默认桥接网络中，因此所有容器互相之间都可以直接访问，这样存在一定的安全性问题。于是有了一个 Docker 引擎参数 <code>--icc=false</code>，当指定该参数后，容器间将默认无法互访，除非互相间使用了 <code>--links</code> 参数的容器才可以互通，并且只有镜像中 <code>EXPOSE</code> 所声明的端口才可以被访问。这个 <code>--icc=false</code> 的用法，在引入了 <code>docker network</code> 后已经基本不用了，通过自定义网络可以很轻松的实现容器间的互联与隔离。</p>
<p>要将 <code>EXPOSE</code> 和在运行时使用 <code>-p &lt;宿主端口&gt;:&lt;容器端口&gt;</code> 区分开来。<code>-p</code>，是映射宿主端口和容器端口，换句话说，就是将容器的对应端口服务公开给外界访问，而 <code>EXPOSE</code> 仅仅是声明容器打算使用什么端口而已，并不会自动在宿主进行端口映射。</p>
<p><strong>WORKDIR 指定工作目录</strong></p>
<p>格式为 <code>WORKDIR &lt;工作目录路径&gt;</code>。</p>
<p>使用 <code>WORKDIR</code> 指令可以来指定工作目录（或者称为当前目录），以后各层的当前目录就被改为指定的目录，如该目录不存在，<code>WORKDIR</code> 会帮你建立目录。</p>
<p>之前提到一些初学者常犯的错误是把 <code>Dockerfile</code> 等同于 Shell 脚本来书写，这种错误的理解还可能会导致出现下面这样的错误：</p>
<figure class="highlight docker"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="bash"> <span class="built_in">cd</span> /app</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> <span class="built_in">echo</span> <span class="string">"hello"</span> &gt; world.txt</span></span><br></pre></td></tr></table></figure>

<p>如果将这个 <code>Dockerfile</code> 进行构建镜像运行后，会发现找不到 <code>/app/world.txt</code> 文件，或者其内容不是 <code>hello</code>。原因其实很简单，在 Shell 中，连续两行是同一个进程执行环境，因此前一个命令修改的内存状态，会直接影响后一个命令；而在 <code>Dockerfile</code> 中，这两行 <code>RUN</code> 命令的执行环境根本不同，是两个完全不同的容器。这就是对 <code>Dockerfile</code> 构建分层存储的概念不了解所导致的错误。</p>
<p>之前说过每一个 <code>RUN</code> 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 <code>RUN cd /app</code> 的执行仅仅是当前进程的工作目录变更，一个内存上的变化而已，其结果不会造成任何文件变更。而到第二层的时候，启动的是一个全新的容器，跟第一层的容器更完全没关系，自然不可能继承前一层构建过程中的内存变化。</p>
<p>因此如果需要改变以后各层的工作目录的位置，那么应该使用 <code>WORKDIR</code> 指令。</p>
<p><strong>USER 指定当前用户</strong></p>
<p>格式：<code>USER &lt;用户名&gt;</code></p>
<p><code>USER</code> 指令和 <code>WORKDIR</code> 相似，都是改变环境状态并影响以后的层。<code>WORKDIR</code> 是改变工作目录，<code>USER</code> 则是改变之后层的执行 <code>RUN</code>, <code>CMD</code> 以及 <code>ENTRYPOINT</code> 这类命令的身份。</p>
<p>当然，和 <code>WORKDIR</code> 一样，<code>USER</code> 只是帮助你切换到指定用户而已，这个用户必须是事先建立好的，否则无法切换。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">RUN groupadd -r redis &amp;&amp; useradd -r -g redis redis</span><br><span class="line">USER redis</span><br><span class="line">RUN [ &quot;redis-server&quot; ]</span><br></pre></td></tr></table></figure>

<p>如果以 <code>root</code> 执行的脚本，在执行期间希望改变身份，比如希望以某个已经建立好的用户来运行某个服务进程，不要使用 <code>su</code> 或者 <code>sudo</code>，这些都需要比较麻烦的配置，而且在 TTY 缺失的环境下经常出错。建议使用 <a href="https://github.com/tianon/gosu" target="_blank" rel="noopener"><code>gosu</code></a><a href="https://github.com/tianon/gosu" target="_blank" rel="noopener"> </a>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 建立 redis 用户，并使用 gosu 换另一个用户执行命令</span><br><span class="line">RUN groupadd -r redis &amp;&amp; useradd -r -g redis redis</span><br><span class="line"># 下载 gosu</span><br><span class="line">RUN wget -O &#x2F;usr&#x2F;local&#x2F;bin&#x2F;gosu &quot;https:&#x2F;&#x2F;github.com&#x2F;tianon&#x2F;gosu&#x2F;releases&#x2F;download&#x2F;1.7&#x2F;gosu-amd64&quot; \</span><br><span class="line">    &amp;&amp; chmod +x &#x2F;usr&#x2F;local&#x2F;bin&#x2F;gosu \</span><br><span class="line">    &amp;&amp; gosu nobody true</span><br><span class="line"># 设置 CMD，并以另外的用户执行</span><br><span class="line">CMD [ &quot;exec&quot;, &quot;gosu&quot;, &quot;redis&quot;, &quot;redis-server&quot; ]</span><br></pre></td></tr></table></figure>

<p><strong>HEALTHCHECK 健康检查</strong></p>
<p>格式：</p>
<ul>
<li><code>HEALTHCHECK [选项] CMD &lt;命令&gt;</code>：设置检查容器健康状况的命令</li>
<li><code>HEALTHCHECK NONE</code>：如果基础镜像有健康检查指令，使用这行可以屏蔽掉其健康检查指令</li>
</ul>
<p><code>HEALTHCHECK</code> 指令是告诉 Docker 应该如何进行判断容器的状态是否正常，这是 Docker 1.12 引入的新指令。</p>
<p>在没有 <code>HEALTHCHECK</code>  指令前，Docker  引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题，但是如果程序进入死锁状态，或者死循环状态，应用进程并不退出，但是该容器已经无法提供服务了。在  1.12 以前，Docker 不会检测到容器的这种状态，从而不会重新调度，导致可能会有部分容器已经无法提供服务了却还在接受用户请求。</p>
<p>而自 1.12 之后，Docker 提供了 <code>HEALTHCHECK</code> 指令，通过该指令指定一行命令，用这行命令来判断容器主进程的服务状态是否还正常，从而比较真实的反应容器实际状态。</p>
<p>当在一个镜像指定了 <code>HEALTHCHECK</code> 指令后，用其启动容器，初始状态会为 <code>starting</code>，在 <code>HEALTHCHECK</code> 指令检查成功后变为 <code>healthy</code>，如果连续一定次数失败，则会变为 <code>unhealthy</code>。</p>
<p><code>HEALTHCHECK</code> 支持下列选项：</p>
<ul>
<li><code>--interval=&lt;间隔&gt;</code>：两次健康检查的间隔，默认为 30 秒；</li>
<li><code>--timeout=&lt;时长&gt;</code>：健康检查命令运行超时时间，如果超过这个时间，本次健康检查就被视为失败，默认 30 秒；</li>
<li><code>--retries=&lt;次数&gt;</code>：当连续失败指定次数后，则将容器状态视为 <code>unhealthy</code>，默认 3 次。</li>
</ul>
<p>和 <code>CMD</code>, <code>ENTRYPOINT</code> 一样，<code>HEALTHCHECK</code> 只可以出现一次，如果写了多个，只有最后一个生效。</p>
<p>在 <code>HEALTHCHECK [选项] CMD</code> 后面的命令，格式和 <code>ENTRYPOINT</code> 一样，分为 <code>shell</code> 格式，和 <code>exec</code> 格式。命令的返回值决定了该次健康检查的成功与否：<code>0</code>：成功；<code>1</code>：失败；<code>2</code>：保留，不要使用这个值。</p>
<p>假设我们有个镜像是个最简单的 Web 服务，我们希望增加健康检查来判断其 Web 服务是否在正常工作，我们可以用 <code>curl</code> 来帮助判断，其 <code>Dockerfile</code> 的 <code>HEALTHCHECK</code> 可以这么写：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> nginx</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; apt-get install -y curl &amp;&amp; rm -rf /var/lib/apt/lists/*</span></span><br><span class="line"><span class="keyword">HEALTHCHECK</span><span class="bash"> --interval=5s --timeout=3s \</span></span><br><span class="line"><span class="bash">  CMD curl -fs http://localhost/ || <span class="built_in">exit</span> 1</span></span><br></pre></td></tr></table></figure>

<p>这里我们设置了每 5 秒检查一次（这里为了试验所以间隔非常短，实际应该相对较长），如果健康检查命令超过 3 秒没响应就视为失败，并且使用 <code>curl -fs http://localhost/ || exit 1</code> 作为健康检查命令。</p>
<p>使用 <code>docker build</code> 来构建这个镜像：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker build -t myweb:v1 .</span><br></pre></td></tr></table></figure>

<p>构建好了后，我们启动一个容器：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d --name web -p 80:80 myweb:v1</span><br></pre></td></tr></table></figure>

<p>当运行该镜像后，可以通过 <code>docker container ls</code> 看到最初的状态为 <code>(health: starting)</code>：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker container ls</span><br><span class="line">CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES</span><br><span class="line">03e28eb00bd0        myweb:v1            <span class="string">"nginx -g 'daemon off"</span>   3 seconds ago       Up 2 seconds (health: starting)   80/tcp, 443/tcp     web</span><br></pre></td></tr></table></figure>

<p>在等待几秒钟后，再次 <code>docker container ls</code>，就会看到健康状态变化为了 <code>(healthy)</code>：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker container ls</span><br><span class="line">CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                    PORTS               NAMES</span><br><span class="line">03e28eb00bd0        myweb:v1            <span class="string">"nginx -g 'daemon off"</span>   18 seconds ago      Up 16 seconds (healthy)   80/tcp, 443/tcp     web</span><br></pre></td></tr></table></figure>

<p>如果健康检查连续失败超过了重试次数，状态就会变为 <code>(unhealthy)</code>。</p>
<p>为了帮助排障，健康检查命令的输出（包括 <code>stdout</code> 以及 <code>stderr</code>）都会被存储于健康状态里，可以用 <code>docker inspect</code> 来查看。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">$ docker inspect --format <span class="string">'&#123;&#123;json .State.Health&#125;&#125;'</span> web | python -m json.tool</span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">"FailingStreak"</span>: 0,</span><br><span class="line">    <span class="string">"Log"</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="string">"End"</span>: <span class="string">"2016-11-25T14:35:37.940957051Z"</span>,</span><br><span class="line">            <span class="string">"ExitCode"</span>: 0,</span><br><span class="line">            <span class="string">"Output"</span>: <span class="string">"&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;\n&lt;title&gt;Welcome to nginx!&lt;/title&gt;\n&lt;style&gt;\n    body &#123;\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    &#125;\n&lt;/style&gt;\n&lt;/head&gt;\n&lt;body&gt;\n&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;\n&lt;p&gt;If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.&lt;/p&gt;\n\n&lt;p&gt;For online documentation and support please refer to\n&lt;a href=\"http://nginx.org/\"&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;\nCommercial support is available at\n&lt;a href=\"http://nginx.com/\"&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;\n\n&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;\n&lt;/body&gt;\n&lt;/html&gt;\n"</span>,</span><br><span class="line">            <span class="string">"Start"</span>: <span class="string">"2016-11-25T14:35:37.780192565Z"</span></span><br><span class="line">        &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">"Status"</span>: <span class="string">"healthy"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>ONBUILD 为他人作嫁衣</strong></p>
<p>格式：<code>ONBUILD &lt;其它指令&gt;</code>。</p>
<p><code>ONBUILD</code> 是一个特殊的指令，它后面跟的是其它指令，比如 <code>RUN</code>, <code>COPY</code> 等，而这些指令，在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像，去构建下一级镜像的时候才会被执行。</p>
<p><code>Dockerfile</code> 中的其它指令都是为了定制当前镜像而准备的，唯有 <code>ONBUILD</code> 是为了帮助别人定制自己而准备的。</p>
<p>假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 <code>npm</code> 进行包管理，所有依赖、配置、启动信息等会放到 <code>package.json</code> 文件里。在拿到程序代码后，需要先进行 <code>npm install</code> 才可以获得所有需要的依赖。然后就可以通过 <code>npm start</code> 来启动应用。因此，一般来说会这样写 <code>Dockerfile</code>：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> node:slim</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> mkdir /app</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> ./package.json /app</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> [ <span class="string">"npm"</span>, <span class="string">"install"</span> ]</span></span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> . /app/</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [ <span class="string">"npm"</span>, <span class="string">"start"</span> ]</span></span><br></pre></td></tr></table></figure>

<p>把这个 <code>Dockerfile</code> 放到 Node.js 项目的根目录，构建好镜像后，就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢？好吧，那就再把这个 <code>Dockerfile</code> 复制到第二个项目里。那如果有第三个项目呢？再复制么？文件的副本越多，版本控制就越困难，让我们继续看这样的场景维护的问题。</p>
<p>如果第一个 Node.js 项目在开发过程中，发现这个 <code>Dockerfile</code> 里存在问题，比如敲错字了、或者需要安装额外的包，然后开发人员修复了这个 <code>Dockerfile</code>，再次构建，问题解决。第一个项目没问题了，但是第二个项目呢？虽然最初 <code>Dockerfile</code> 是复制、粘贴自第一个项目的，但是并不会因为第一个项目修复了他们的 <code>Dockerfile</code>，而第二个项目的 <code>Dockerfile</code> 就会被自动修复。</p>
<p>那么我们可不可以做一个基础镜像，然后各个项目使用这个基础镜像呢？这样基础镜像更新，各个项目不用同步 <code>Dockerfile</code> 的变化，重新构建后就继承了基础镜像的更新？好吧，可以，让我们看看这样的结果。那么上面的这个 <code>Dockerfile</code> 就会变为：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> node:slim</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> mkdir /app</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /app</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [ <span class="string">"npm"</span>, <span class="string">"start"</span> ]</span></span><br></pre></td></tr></table></figure>

<p>这里我们把项目相关的构建指令拿出来，放到子项目里去。假设这个基础镜像的名字为 <code>my-node</code> 的话，各个项目内的自己的 <code>Dockerfile</code> 就变为：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> my-node</span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> ./package.json /app</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> [ <span class="string">"npm"</span>, <span class="string">"install"</span> ]</span></span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> . /app/</span></span><br></pre></td></tr></table></figure>

<p>基础镜像变化后，各个项目都用这个 <code>Dockerfile</code> 重新构建镜像，会继承基础镜像的更新。</p>
<p>那么，问题解决了么？没有。准确说，只解决了一半。如果这个 <code>Dockerfile</code> 里面有些东西需要调整呢？比如 <code>npm install</code> 都需要加一些参数，那怎么办？这一行 <code>RUN</code> 是不可能放入基础镜像的，因为涉及到了当前项目的 <code>./package.json</code>，难道又要一个个修改么？所以说，这样制作基础镜像，只解决了原来的 <code>Dockerfile</code> 的前4条指令的变化问题，而后面三条指令的变化则完全没办法处理。</p>
<p><code>ONBUILD</code> 可以解决这个问题。让我们用 <code>ONBUILD</code> 重新写一下基础镜像的 <code>Dockerfile</code>:</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> node:slim</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> mkdir /app</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /app</span></span><br><span class="line"><span class="keyword">ONBUILD</span> <span class="keyword">COPY</span><span class="bash"> ./package.json /app</span></span><br><span class="line"><span class="keyword">ONBUILD</span> <span class="keyword">RUN</span><span class="bash"> [ <span class="string">"npm"</span>, <span class="string">"install"</span> ]</span></span><br><span class="line"><span class="keyword">ONBUILD</span> <span class="keyword">COPY</span><span class="bash"> . /app/</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [ <span class="string">"npm"</span>, <span class="string">"start"</span> ]</span></span><br></pre></td></tr></table></figure>

<p>这次我们回到原始的 <code>Dockerfile</code>，但是这次将项目相关的指令加上 <code>ONBUILD</code>，这样在构建基础镜像的时候，这三行并不会被执行。然后各个项目的 <code>Dockerfile</code> 就变成了简单地：</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> my-node</span><br></pre></td></tr></table></figure>

<p>是的，只有这么一行。当在各个项目目录中，用这个只有一行的 <code>Dockerfile</code> 构建镜像时，之前基础镜像的那三行 <code>ONBUILD</code> 就会开始执行，成功的将当前项目的代码复制进镜像、并且针对本项目执行 <code>npm install</code>，生成应用镜像。</p>
<p><strong>参考文档</strong></p>
<ul>
<li><a href="https://docs.docker.com/engine/reference/builder/" target="_blank" rel="noopener">Dockerfie 官方文档</a><a href="https://docs.docker.com/engine/reference/builder/" target="_blank" rel="noopener"> </a></li>
<li><a href="https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/" target="_blank" rel="noopener">Dockerfile 最佳实践文档</a><a href="https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/" target="_blank" rel="noopener"> </a></li>
<li><a href="https://github.com/docker-library/docs" target="_blank" rel="noopener">Docker 官方镜像 Dockerfile</a><a href="https://github.com/docker-library/docs" target="_blank" rel="noopener"> </a></li>
</ul>
<h2 id="Docker-三剑客-Compose"><a href="#Docker-三剑客-Compose" class="headerlink" title="Docker 三剑客 Compose"></a>Docker 三剑客 Compose</h2><h3 id="Docker-Compose-简介"><a href="#Docker-Compose-简介" class="headerlink" title="Docker Compose 简介"></a>Docker Compose 简介</h3><p><code>Compose</code> 项目是 Docker 官方的开源项目，负责实现对 Docker 容器集群的快速编排。从功能上看，跟 <code>OpenStack</code> 中的 <code>Heat</code> 十分类似。</p>
<p>其代码目前在 <a href="https://github.com/docker/compose" target="_blank" rel="noopener">https://github.com/docker/compose</a>上开源。</p>
<p><code>Compose</code> 定位是 「定义和运行多个 Docker 容器的应用（Defining and running multi-container Docker applications）」，其前身是开源项目 Fig。</p>
<p>通过第一部分中的介绍，我们知道使用一个 <code>Dockerfile</code> 模板文件，可以让用户很方便的定义一个单独的应用容器。然而，在日常工作中，经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目，除了 Web 服务容器本身，往往还需要再加上后端的数据库服务容器，甚至还包括负载均衡容器等。</p>
<p><code>Compose</code> 恰好满足了这样的需求。它允许用户通过一个单独的 <code>docker-compose.yml</code> 模板文件（YAML 格式）来定义一组相关联的应用容器为一个项目（project）。</p>
<p><code>Compose</code> 中有两个重要的概念：</p>
<ul>
<li>服务 (<code>service</code>)：一个应用的容器，实际上可以包括若干运行相同镜像的容器实例。</li>
<li>项目 (<code>project</code>)：由一组关联的应用容器组成的一个完整业务单元，在 <code>docker-compose.yml</code> 文件中定义。</li>
</ul>
<p><code>Compose</code> 的默认管理对象是项目，通过子命令对项目中的一组容器进行便捷地生命周期管理。</p>
<p><code>Compose</code> 项目由 Python 编写，实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此，只要所操作的平台支持 Docker API，就可以在其上利用 <code>Compose</code> 来进行编排管理。</p>
<h3 id="Docker-Compose-安装与卸载"><a href="#Docker-Compose-安装与卸载" class="headerlink" title="Docker Compose 安装与卸载"></a>Docker Compose 安装与卸载</h3><p><code>Compose</code> 支持 Linux、macOS、Windows 10 三大平台。</p>
<p><code>Compose</code> 可以通过 Python 的包管理工具 <code>pip</code> 进行安装，也可以直接下载编译好的二进制文件使用，甚至能够直接在 Docker 容器中运行。</p>
<p>前两种方式是传统方式，适合本地环境下安装使用；最后一种方式则不破坏系统环境，更适合云计算场景。</p>
<p><code>Docker for Mac</code> 、<code>Docker for Windows</code> 自带 <code>docker-compose</code> 二进制文件，安装 Docker 之后可以直接使用。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose --version</span><br><span class="line"></span><br><span class="line">docker-compose version 1.17.1, build 6d101fb</span><br></pre></td></tr></table></figure>

<p>Linux 系统请使用以下介绍的方法安装。</p>
<p><strong>二进制包</strong></p>
<p>在 Linux 上的也安装十分简单，从 <a href="https://github.com/docker/compose/releases" target="_blank" rel="noopener">官方 GitHub Release</a></p>
<p> 处直接下载编译好的二进制文件即可。</p>
<p>例如，在 Linux 64 位系统上直接下载对应的二进制包。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` &gt; /usr/<span class="built_in">local</span>/bin/docker-compose</span><br><span class="line">$ sudo chmod +x /usr/<span class="built_in">local</span>/bin/docker-compose</span><br></pre></td></tr></table></figure>

<p><strong>PIP 安装</strong></p>
<p><em>注：</em> <code>x86_64</code> 架构的 Linux 建议按照上边的方法下载二进制包进行安装，如果您计算机的架构是 <code>ARM</code> (例如，树莓派)，再使用 <code>pip</code> 安装。</p>
<p>这种方式是将 Compose 当作一个 Python 应用来从 pip 源中安装。</p>
<p>执行安装命令：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo pip install -U docker-compose</span><br></pre></td></tr></table></figure>

<p>可以看到类似如下输出，说明安装成功。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Collecting docker-compose</span><br><span class="line">  Downloading docker-compose-1.17.1.tar.gz (149kB): 149kB downloaded</span><br><span class="line">...</span><br><span class="line">Successfully installed docker-compose cached-property requests texttable websocket-client docker-py dockerpty six enum34 backports.ssl-match-hostname ipaddress</span><br></pre></td></tr></table></figure>

<p><strong>bash 补全命令</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ curl -L https://raw.githubusercontent.com/docker/compose/1.8.0/contrib/completion/bash/docker-compose &gt; /etc/bash_completion.d/docker-compose</span><br></pre></td></tr></table></figure>

<p><strong>容器中执行</strong></p>
<p>Compose 既然是一个 Python 应用，自然也可以直接用容器来执行它。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ curl -L https://github.com/docker/compose/releases/download/1.8.0/run.sh &gt; /usr/<span class="built_in">local</span>/bin/docker-compose</span><br><span class="line">$ chmod +x /usr/<span class="built_in">local</span>/bin/docker-compose</span><br></pre></td></tr></table></figure>

<p>实际上，查看下载的 <code>run.sh</code> 脚本内容，如下</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line">VERSION=<span class="string">"1.8.0"</span></span><br><span class="line">IMAGE=<span class="string">"docker/compose:<span class="variable">$VERSION</span>"</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># Setup options for connecting to docker host</span></span><br><span class="line"><span class="keyword">if</span> [ -z <span class="string">"<span class="variable">$DOCKER_HOST</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">    DOCKER_HOST=<span class="string">"/var/run/docker.sock"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="keyword">if</span> [ -S <span class="string">"<span class="variable">$DOCKER_HOST</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">    DOCKER_ADDR=<span class="string">"-v <span class="variable">$DOCKER_HOST</span>:<span class="variable">$DOCKER_HOST</span> -e DOCKER_HOST"</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    DOCKER_ADDR=<span class="string">"-e DOCKER_HOST -e DOCKER_TLS_VERIFY -e DOCKER_CERT_PATH"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># Setup volume mounts for compose config and context</span></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">"<span class="variable">$(pwd)</span>"</span> != <span class="string">'/'</span> ]; <span class="keyword">then</span></span><br><span class="line">    VOLUMES=<span class="string">"-v <span class="variable">$(pwd)</span>:<span class="variable">$(pwd)</span>"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="keyword">if</span> [ -n <span class="string">"<span class="variable">$COMPOSE_FILE</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">    compose_dir=$(dirname <span class="variable">$COMPOSE_FILE</span>)</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="comment"># <span class="doctag">TODO:</span> also check --file argument</span></span><br><span class="line"><span class="keyword">if</span> [ -n <span class="string">"<span class="variable">$compose_dir</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">    VOLUMES=<span class="string">"<span class="variable">$VOLUMES</span> -v <span class="variable">$compose_dir</span>:<span class="variable">$compose_dir</span>"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="keyword">if</span> [ -n <span class="string">"<span class="variable">$HOME</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">    VOLUMES=<span class="string">"<span class="variable">$VOLUMES</span> -v <span class="variable">$HOME</span>:<span class="variable">$HOME</span> -v <span class="variable">$HOME</span>:/root"</span> <span class="comment"># mount $HOME in /root to share docker.config</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Only allocate tty if we detect one</span></span><br><span class="line"><span class="keyword">if</span> [ -t 1 ]; <span class="keyword">then</span></span><br><span class="line">    DOCKER_RUN_OPTIONS=<span class="string">"-t"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="keyword">if</span> [ -t 0 ]; <span class="keyword">then</span></span><br><span class="line">    DOCKER_RUN_OPTIONS=<span class="string">"<span class="variable">$DOCKER_RUN_OPTIONS</span> -i"</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">exec</span> docker run --rm <span class="variable">$DOCKER_RUN_OPTIONS</span> <span class="variable">$DOCKER_ADDR</span> <span class="variable">$COMPOSE_OPTIONS</span> <span class="variable">$VOLUMES</span> -w <span class="string">"<span class="variable">$(pwd)</span>"</span> <span class="variable">$IMAGE</span> <span class="string">"<span class="variable">$@</span>"</span></span><br></pre></td></tr></table></figure>

<p>可以看到，它其实是下载了 <code>docker/compose</code> 镜像并运行。</p>
<p><strong>卸载</strong></p>
<p>如果是二进制包方式安装的，删除二进制文件即可。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo rm /usr/<span class="built_in">local</span>/bin/docker-compose</span><br></pre></td></tr></table></figure>

<p>如果是通过 <code>pip</code> 安装的，则执行如下命令即可删除。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo pip uninstall docker-compose</span><br></pre></td></tr></table></figure>

<h3 id="Docker-Compose-使用"><a href="#Docker-Compose-使用" class="headerlink" title="Docker Compose 使用"></a>Docker Compose 使用</h3><p><strong>术语</strong></p>
<p>首先介绍几个术语。</p>
<ul>
<li>服务 (<code>service</code>)：一个应用容器，实际上可以运行多个相同镜像的实例。</li>
<li>项目 (<code>project</code>)：由一组关联的应用容器组成的一个完整业务单元。</li>
</ul>
<p>可见，一个项目可以由多个服务（容器）关联而成，<code>Compose</code> 面向项目进行管理。</p>
<p><strong>场景</strong></p>
<p>最常见的项目是 web 网站，该项目应该包含 web 应用和缓存。</p>
<p>下面我们用 <code>Python</code> 来建立一个能够记录页面访问次数的 web 网站。</p>
<p><strong>web 应用</strong></p>
<p>新建文件夹，在该目录中编写 <code>app.py</code> 文件</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask</span><br><span class="line"><span class="keyword">from</span> redis <span class="keyword">import</span> Redis</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">redis = Redis(host=<span class="string">'redis'</span>, port=<span class="number">6379</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route('/')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">hello</span><span class="params">()</span>:</span></span><br><span class="line">    count = redis.incr(<span class="string">'hits'</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="string">'Hello World! 该页面已被访问 &#123;&#125; 次。\n'</span>.format(count)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">    app.run(host=<span class="string">"0.0.0.0"</span>, debug=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure>

<p><strong>Dockerfile</strong></p>
<p>编写 <code>Dockerfile</code> 文件，内容为</p>
<figure class="highlight docker"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> python:<span class="number">3.6</span>-alpine</span><br><span class="line"><span class="keyword">ADD</span><span class="bash"> . /code</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /code</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> pip install redis flask</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [<span class="string">"python"</span>, <span class="string">"app.py"</span>]</span></span><br></pre></td></tr></table></figure>

<p><strong>docker-compose.yml</strong></p>
<p>编写 <code>docker-compose.yml</code> 文件，这个是 Compose 使用的主模板文件。</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">'3'</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">.</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">     <span class="bullet">-</span> <span class="string">"5000:5000"</span></span><br><span class="line">     </span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">"redis:alpine"</span></span><br></pre></td></tr></table></figure>

<p><strong>运行 compose 项目</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose up</span><br></pre></td></tr></table></figure>

<p>此时访问本地 </p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">5000</span><br></pre></td></tr></table></figure>

<p> 端口，每次刷新页面，计数就会加 1。</p>
<h3 id="Docker-Compose-命令说明"><a href="#Docker-Compose-命令说明" class="headerlink" title="Docker Compose 命令说明"></a>Docker Compose 命令说明</h3><p><strong>命令对象与格式</strong></p>
<p>对于 Compose 来说，大部分命令的对象既可以是项目本身，也可以指定为项目中的服务或者容器。如果没有特别的说明，命令对象将是项目，这意味着项目中所有的服务都会受到命令影响。</p>
<p>执行 <code>docker-compose [COMMAND] --help</code> 或者 <code>docker-compose help [COMMAND]</code> 可以查看具体某个命令的使用格式。</p>
<p><code>docker-compose</code> 命令的基本的使用格式是</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose [-f=&lt;arg&gt;...] [options] [COMMAND] [ARGS...]</span><br></pre></td></tr></table></figure>

<p><strong>命令选项</strong></p>
<ul>
<li><code>-f, --file FILE</code> 指定使用的 Compose 模板文件，默认为 <code>docker-compose.yml</code>，可以多次指定。</li>
<li><code>-p, --project-name NAME</code> 指定项目名称，默认将使用所在目录名称作为项目名。</li>
<li><code>--x-networking</code> 使用 Docker 的可拔插网络后端特性</li>
<li><code>--x-network-driver DRIVER</code> 指定网络后端的驱动，默认为 <code>bridge</code></li>
<li><code>--verbose</code> 输出更多调试信息。</li>
<li><code>-v, --version</code> 打印版本并退出。</li>
</ul>
<p><strong>命令使用说明</strong></p>
<p><code>build</code></p>
<p>格式为 <code>docker-compose build [options] [SERVICE...]</code>。</p>
<p>构建（重新构建）项目中的服务容器。</p>
<p>服务容器一旦构建后，将会带上一个标记名，例如对于 web 项目中的一个 db 容器，可能是 web_db。</p>
<p>可以随时在项目目录下运行 <code>docker-compose build</code> 来重新构建服务。</p>
<p>选项包括：</p>
<ul>
<li><code>--force-rm</code> 删除构建过程中的临时容器。</li>
<li><code>--no-cache</code> 构建镜像过程中不使用 cache（这将加长构建过程）。</li>
<li><code>--pull</code> 始终尝试通过 pull 来获取更新版本的镜像。</li>
</ul>
<p><code>config</code></p>
<p>验证 Compose 文件格式是否正确，若正确则显示配置，若格式错误显示错误原因。</p>
<p><code>down</code></p>
<p>此命令将会停止 <code>up</code> 命令所启动的容器，并移除网络</p>
<p><code>exec</code></p>
<p>进入指定的容器。</p>
<p><code>help</code></p>
<p>获得一个命令的帮助。</p>
<p><code>images</code></p>
<p>列出 Compose 文件中包含的镜像。</p>
<p><code>kill</code></p>
<p>格式为 <code>docker-compose kill [options] [SERVICE...]</code>。</p>
<p>通过发送 <code>SIGKILL</code> 信号来强制停止服务容器。</p>
<p>支持通过 <code>-s</code> 参数来指定发送的信号，例如通过如下指令发送 <code>SIGINT</code> 信号。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose <span class="built_in">kill</span> -s SIGINT</span><br></pre></td></tr></table></figure>

<p><code>logs</code></p>
<p>格式为 <code>docker-compose logs [options] [SERVICE...]</code>。</p>
<p>查看服务容器的输出。默认情况下，docker-compose 将对不同的服务输出使用不同的颜色来区分。可以通过 <code>--no-color</code> 来关闭颜色。</p>
<p>该命令在调试问题的时候十分有用。</p>
<p><code>pause</code></p>
<p>格式为 <code>docker-compose pause [SERVICE...]</code>。</p>
<p>暂停一个服务容器。</p>
<p><code>port</code></p>
<p>格式为 <code>docker-compose port [options] SERVICE PRIVATE_PORT</code>。</p>
<p>打印某个容器端口所映射的公共端口。</p>
<p>选项：</p>
<ul>
<li><code>--protocol=proto</code> 指定端口协议，tcp（默认值）或者 udp。</li>
<li><code>--index=index</code> 如果同一服务存在多个容器，指定命令对象容器的序号（默认为 1）。</li>
</ul>
<p><code>ps</code></p>
<p>格式为 <code>docker-compose ps [options] [SERVICE...]</code>。</p>
<p>列出项目中目前的所有容器。</p>
<p>选项：</p>
<ul>
<li><code>-q</code> 只打印容器的 ID 信息。</li>
</ul>
<p><code>pull</code></p>
<p>格式为 <code>docker-compose pull [options] [SERVICE...]</code>。</p>
<p>拉取服务依赖的镜像。</p>
<p>选项：</p>
<ul>
<li><code>--ignore-pull-failures</code> 忽略拉取镜像过程中的错误。</li>
</ul>
<p><code>push</code></p>
<p>推送服务依赖的镜像到 Docker 镜像仓库。</p>
<p><code>restart</code></p>
<p>格式为 <code>docker-compose restart [options] [SERVICE...]</code>。</p>
<p>重启项目中的服务。</p>
<p>选项：</p>
<ul>
<li><code>-t, --timeout TIMEOUT</code> 指定重启前停止容器的超时（默认为 10 秒）。</li>
</ul>
<p><code>rm</code></p>
<p>格式为 <code>docker-compose rm [options] [SERVICE...]</code>。</p>
<p>删除所有（停止状态的）服务容器。推荐先执行 <code>docker-compose stop</code> 命令来停止容器。</p>
<p>选项：</p>
<ul>
<li><code>-f, --force</code> 强制直接删除，包括非停止状态的容器。一般尽量不要使用该选项。</li>
<li><code>-v</code> 删除容器所挂载的数据卷。</li>
</ul>
<p><code>run</code></p>
<p>格式为 <code>docker-compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]</code>。</p>
<p>在指定服务上执行一个命令。</p>
<p>例如：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose run ubuntu ping docker.com</span><br></pre></td></tr></table></figure>

<p>将会启动一个 ubuntu 服务容器，并执行 <code>ping docker.com</code> 命令。</p>
<p>默认情况下，如果存在关联，则所有关联的服务将会自动被启动，除非这些服务已经在运行中。</p>
<p>该命令类似启动容器后运行指定的命令，相关卷、链接等等都将会按照配置自动创建。</p>
<p>两个不同点：</p>
<ul>
<li>给定命令将会覆盖原有的自动运行命令；</li>
<li>不会自动创建端口，以避免冲突。</li>
</ul>
<p>如果不希望自动启动关联的容器，可以使用 <code>--no-deps</code> 选项，例如</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose run --no-deps web python manage.py shell</span><br></pre></td></tr></table></figure>

<p>将不会启动 web 容器所关联的其它容器。</p>
<p>选项：</p>
<ul>
<li><code>-d</code> 后台运行容器。</li>
<li><code>--name NAME</code> 为容器指定一个名字。</li>
<li><code>--entrypoint CMD</code> 覆盖默认的容器启动指令。</li>
<li><code>-e KEY=VAL</code> 设置环境变量值，可多次使用选项来设置多个环境变量。</li>
<li><code>-u, --user=&quot;&quot;</code> 指定运行容器的用户名或者 uid。</li>
<li><code>--no-deps</code> 不自动启动关联的服务容器。</li>
<li><code>--rm</code> 运行命令后自动删除容器，<code>d</code> 模式下将忽略。</li>
<li><code>-p, --publish=[]</code> 映射容器端口到本地主机。</li>
<li><code>--service-ports</code> 配置服务端口并映射到本地主机。</li>
<li><code>-T</code> 不分配伪 tty，意味着依赖 tty 的指令将无法运行。</li>
</ul>
<p><code>scale</code></p>
<p>格式为 <code>docker-compose scale [options] [SERVICE=NUM...]</code>。</p>
<p>设置指定服务运行的容器个数。</p>
<p>通过 <code>service=num</code> 的参数来设置数量。例如：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose scale web=3 db=2</span><br></pre></td></tr></table></figure>

<p>将启动 3 个容器运行 web 服务，2 个容器运行 db 服务。</p>
<p>一般的，当指定数目多于该服务当前实际运行容器，将新创建并启动容器；反之，将停止容器。</p>
<p>选项：</p>
<ul>
<li><code>-t, --timeout TIMEOUT</code> 停止容器时候的超时（默认为 10 秒）。</li>
</ul>
<p><code>start</code></p>
<p>格式为 <code>docker-compose start [SERVICE...]</code>。</p>
<p>启动已经存在的服务容器。</p>
<p><code>stop</code></p>
<p>格式为 <code>docker-compose stop [options] [SERVICE...]</code>。</p>
<p>停止已经处于运行状态的容器，但不删除它。通过 <code>docker-compose start</code> 可以再次启动这些容器。</p>
<p>选项：</p>
<ul>
<li><p><code>-t, --timeout TIMEOUT</code> 停止容器时候的超时（默认为 10 秒）。</p>
<p><code>top</code></p>
</li>
</ul>
<p>查看各个服务容器内运行的进程。</p>
<p> <code>unpause</code></p>
<p>格式为 <code>docker-compose unpause [SERVICE...]</code>。</p>
<p>恢复处于暂停状态中的服务。</p>
<p><code>up</code></p>
<p>格式为 <code>docker-compose up [options] [SERVICE...]</code>。</p>
<p>该命令十分强大，它将尝试自动完成包括构建镜像，（重新）创建服务，启动服务，并关联服务相关容器的一系列操作。</p>
<p>链接的服务都将会被自动启动，除非已经处于运行状态。</p>
<p>可以说，大部分时候都可以直接通过该命令来启动一个项目。</p>
<p>默认情况，<code>docker-compose up</code> 启动的容器都在前台，控制台将会同时打印所有容器的输出信息，可以很方便进行调试。</p>
<p>当通过 <code>Ctrl-C</code> 停止命令时，所有容器将会停止。</p>
<p>如果使用 <code>docker-compose up -d</code>，将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。</p>
<p>默认情况，如果服务容器已经存在，<code>docker-compose up</code> 将会尝试停止容器，然后重新创建（保持使用 <code>volumes-from</code> 挂载的卷），以保证新启动的服务匹配 <code>docker-compose.yml</code> 文件的最新内容。如果用户不希望容器被停止并重新创建，可以使用 <code>docker-compose up --no-recreate</code>。这样将只会启动处于停止状态的容器，而忽略已经运行的服务。如果用户只想重新部署某个服务，可以使用 <code>docker-compose up --no-deps -d &lt;SERVICE_NAME&gt;</code> 来重新创建服务并后台停止旧服务，启动新服务，并不会影响到其所依赖的服务。</p>
<p>选项：</p>
<ul>
<li><code>-d</code> 在后台运行服务容器。</li>
<li><code>--no-color</code> 不使用颜色来区分不同的服务的控制台输出。</li>
<li><code>--no-deps</code> 不启动服务所链接的容器。</li>
<li><code>--force-recreate</code> 强制重新创建容器，不能与 <code>--no-recreate</code> 同时使用。</li>
<li><code>--no-recreate</code> 如果容器已经存在了，则不重新创建，不能与 <code>--force-recreate</code> 同时使用。</li>
<li><code>--no-build</code> 不自动构建缺失的服务镜像。</li>
<li><code>-t, --timeout TIMEOUT</code> 停止容器时候的超时（默认为 10 秒）。</li>
</ul>
<p><code>version</code></p>
<p>格式为 <code>docker-compose version</code>。</p>
<p>打印版本信息。</p>
<h2 id="Docker-三剑客-Swarm"><a href="#Docker-三剑客-Swarm" class="headerlink" title="Docker 三剑客 Swarm"></a>Docker 三剑客 Swarm</h2><h3 id="什么是-Docker-Swarm"><a href="#什么是-Docker-Swarm" class="headerlink" title="什么是 Docker Swarm"></a>什么是 Docker Swarm</h3><p>Docker Swarm 是 Docker 官方三剑客项目之一，提供 Docker 容器集群服务，是 Docker 官方对容器云生态进行支持的核心方案。</p>
<p>使用它，用户可以将多个 Docker 主机封装为单个大型的虚拟 Docker 主机，快速打造一套容器云平台。</p>
<p>注意：Docker 1.12.0+ <a href="https://docs.docker.com/engine/swarm/" target="_blank" rel="noopener">Swarm mode</a><a href="https://docs.docker.com/engine/swarm/" target="_blank" rel="noopener"> </a> 已经内嵌入 Docker 引擎，成为了 docker 子命令 <code>docker swarm</code>，绝大多数用户已经开始使用 <code>Swarm mode</code>，Docker 引擎 API 已经删除 Docker Swarm。为避免大家混淆旧的 <code>Docker Swarm</code> 与新的 <code>Swarm mode</code>，旧的 <code>Docker Swarm</code> 内容已经删除。</p>
<h3 id="Docker-Swarm-mode"><a href="#Docker-Swarm-mode" class="headerlink" title="Docker Swarm mode"></a>Docker Swarm mode</h3><p>Docker 1.12 <a href="https://docs.docker.com/engine/swarm/" target="_blank" rel="noopener">Swarm mode</a><a href="https://docs.docker.com/engine/swarm/" target="_blank" rel="noopener"> </a> 已经内嵌入 Docker 引擎，成为了 docker 子命令 <code>docker swarm</code>。请注意与旧的 <code>Docker Swarm</code> 区分开来。</p>
<p><code>Swarm mode</code> 内置 kv 存储功能，提供了众多的新特性，比如：具有容错能力的去中心化设计、内置服务发现、负载均衡、路由网格、动态伸缩、滚动更新、安全传输等。使得 Docker 原生的 <code>Swarm</code> 集群具备与 Mesos、Kubernetes 竞争的实力。</p>
<h3 id="Swarm-mode-基本概念"><a href="#Swarm-mode-基本概念" class="headerlink" title="Swarm mode 基本概念"></a>Swarm mode 基本概念</h3><p><code>Swarm</code> 是使用 <a href="https://github.com/docker/swarmkit/" target="_blank" rel="noopener"><code>SwarmKit</code></a><a href="https://github.com/docker/swarmkit/" target="_blank" rel="noopener"> </a> 构建的 Docker 引擎内置（原生）的集群管理和编排工具。</p>
<p>使用 <code>Swarm</code> 集群之前需要了解以下几个概念。</p>
<p><strong>节点</strong></p>
<p>运行 Docker 的主机可以主动初始化一个 <code>Swarm</code> 集群或者加入一个已存在的 <code>Swarm</code> 集群，这样这个运行 Docker 的主机就成为一个 <code>Swarm</code> 集群的节点 (<code>node</code>) 。</p>
<p>节点分为管理 (<code>manager</code>) 节点和工作 (<code>worker</code>) 节点。</p>
<p>管理节点用于 <code>Swarm</code> 集群的管理，<code>docker swarm</code> 命令基本只能在管理节点执行（节点退出集群命令 <code>docker swarm leave</code> 可以在工作节点执行）。一个 <code>Swarm</code> 集群可以有多个管理节点，但只有一个管理节点可以成为 <code>leader</code>，<code>leader</code> 通过 <code>raft</code> 协议实现。</p>
<p>工作节点是任务执行节点，管理节点将服务 (<code>service</code>) 下发至工作节点执行。管理节点默认也作为工作节点。你也可以通过配置让服务只运行在管理节点。</p>
<p>来自 Docker 官网的这张图片形象的展示了集群中管理节点与工作节点的关系。</p>
<p><img src="/myblog/2020/03/30/Docker/swarm-diagram.png" alt="img"></p>
<p><strong>服务和任务</strong></p>
<p>任务 （<code>Task</code>）是 <code>Swarm</code> 中的最小的调度单位，目前来说就是一个单一的容器。</p>
<p>服务 （<code>Services</code>） 是指一组任务的集合，服务定义了任务的属性。服务有两种模式：</p>
<ul>
<li><code>replicated services</code> 按照一定规则在各个工作节点上运行指定个数的任务。</li>
<li><code>global services</code> 每个工作节点上运行一个任务</li>
</ul>
<p>两种模式通过 <code>docker service create</code> 的 <code>--mode</code> 参数指定。</p>
<p>来自 Docker 官网的这张图片形象的展示了容器、任务、服务的关系。</p>
<p><img src="/myblog/2020/03/30/Docker/services-diagram.png" alt="img"></p>
<h2 id="Docker-三剑客-Machine"><a href="#Docker-三剑客-Machine" class="headerlink" title="Docker 三剑客 Machine"></a>Docker 三剑客 Machine</h2><h3 id="什么是-Docker-Machine"><a href="#什么是-Docker-Machine" class="headerlink" title="什么是 Docker Machine"></a>什么是 Docker Machine</h3><p><img src="/myblog/2020/03/30/Docker/machine.png" alt="img"></p>
<p>Docker Machine 是 Docker 官方编排（Orchestration）项目之一，负责在多种平台上快速安装 Docker 环境。</p>
<p>Docker Machine 项目基于 Go 语言实现，目前在 <a href="https://github.com/docker/machine" target="_blank" rel="noopener">Github</a>上进行维护。本章将介绍 Docker Machine 的安装及使用。</p>
<h3 id="Docker-Machine-安装"><a href="#Docker-Machine-安装" class="headerlink" title="Docker Machine 安装"></a>Docker Machine 安装</h3><p>Docker Machine 可以在多种操作系统平台上安装，包括 Linux、macOS，以及 Windows。</p>
<p><strong>macOS、Windows</strong></p>
<p>Docker for Mac、Docker for Windows 自带 <code>docker-machine</code> 二进制包，安装之后即可使用。</p>
<p>查看版本信息。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker-machine -v</span><br><span class="line">docker-machine version 0.13.0, build 9ba6da9</span><br></pre></td></tr></table></figure>

<p><strong>Linux</strong></p>
<p>在 Linux 上的也安装十分简单，从 <a href="https://github.com/docker/machine/releases" target="_blank" rel="noopener">官方 GitHub Release</a></p>
<p> 处直接下载编译好的二进制文件即可。</p>
<p>例如，在 Linux 64 位系统上直接下载对应的二进制包。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo curl -L https://github.com/docker/machine/releases/download/v0.13.0/docker-machine-`uname -s`-`uname -m` &gt; /usr/<span class="built_in">local</span>/bin/docker-machine</span><br><span class="line">$ sudo chmod +x /usr/<span class="built_in">local</span>/bin/docker-machine</span><br></pre></td></tr></table></figure>

<p>完成后，查看版本信息。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ docker-machine -v</span><br><span class="line">docker-machine version 0.13.0, build 9ba6da9</span><br></pre></td></tr></table></figure>

<h3 id="Docker-Machine-使用"><a href="#Docker-Machine-使用" class="headerlink" title="Docker Machine 使用"></a>Docker Machine 使用</h3><p>Docker Machine 支持多种后端驱动，包括虚拟机、本地主机和云平台等。</p>
<h3 id="创建本地主机实例"><a href="#创建本地主机实例" class="headerlink" title="创建本地主机实例"></a>创建本地主机实例</h3><p><strong>Virtualbox 驱动</strong></p>
<p>使用 <code>virtualbox</code> 类型的驱动，创建一台 Docker 主机，命名为 test。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-machine create -d virtualbox <span class="built_in">test</span></span><br></pre></td></tr></table></figure>

<p>你也可以在创建时加上如下参数，来配置主机或者主机上的 Docker。</p>
<p><code>--engine-opt dns=114.114.114.114</code> 配置 Docker 的默认 DNS</p>
<p><code>--engine-registry-mirror https://registry.docker-cn.com</code> 配置 Docker 的仓库镜像</p>
<p><code>--virtualbox-memory 2048</code> 配置主机内存</p>
<p><code>--virtualbox-cpu-count 2</code> 配置主机 CPU</p>
<p>更多参数请使用 <code>docker-machine create --driver virtualbox --help</code> 命令查看。</p>
<p><strong>macOS xhyve 驱动</strong></p>
<p><code>xhyve</code> 驱动 GitHub: <a href="https://github.com/zchee/docker-machine-driver-xhyve" target="_blank" rel="noopener">https://github.com/zchee/docker-machine-driver-xhyve</a></p>
<p><a href="https://github.com/mist64/xhyve" target="_blank" rel="noopener"><code>xhyve</code></a></p>
<p> 是 macOS 上轻量化的虚拟引擎，使用其创建的 Docker Machine 较 <code>VirtualBox</code> 驱动创建的运行效率要高。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ brew install docker-machine-driver-xhyve</span><br><span class="line"></span><br><span class="line">$ docker-machine create \</span><br><span class="line">      -d xhyve \</span><br><span class="line">      <span class="comment"># --xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso \</span></span><br><span class="line">      --engine-opt dns=114.114.114.114 \</span><br><span class="line">      --engine-registry-mirror https://registry.docker-cn.com \</span><br><span class="line">      --xhyve-memory-size 2048 \</span><br><span class="line">      --xhyve-rawdisk \</span><br><span class="line">      --xhyve-cpu-count 2 \</span><br><span class="line">      xhyve</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意：非首次创建时建议加上 <code>--xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso</code> 参数，避免每次创建时都从 GitHub 下载 ISO 镜像。</p>
</blockquote>
<p>更多参数请使用 <code>docker-machine create --driver xhyve --help</code> 命令查看。</p>
<p><strong>Windows 10</strong></p>
<p>Windows 10 安装 Docker for Windows 之后不能再安装 VirtualBox，也就不能使用 <code>virtualbox</code> 驱动来创建 Docker Machine，我们可以选择使用 <code>hyperv</code> 驱动。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-machine create --driver hyperv vm</span><br></pre></td></tr></table></figure>

<p>更多参数请使用 <code>docker-machine create --driver hyperv --help</code> 命令查看。</p>
<p><strong>使用介绍</strong></p>
<p>创建好主机之后，查看主机</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker-machine ls</span><br><span class="line"></span><br><span class="line">NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER       ERRORS</span><br><span class="line"><span class="built_in">test</span>      -        virtualbox   Running   tcp://192.168.99.187:2376           v17.10.0-ce</span><br></pre></td></tr></table></figure>

<p>创建主机成功后，可以通过 <code>env</code> 命令来让后续操作对象都是目标主机。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-machine env <span class="built_in">test</span></span><br></pre></td></tr></table></figure>

<p>后续根据提示在命令行输入命令之后就可以操作 test 主机。</p>
<p>也可以通过 <code>SSH</code> 登录到主机。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ docker-machine ssh <span class="built_in">test</span></span><br><span class="line"></span><br><span class="line">docker@<span class="built_in">test</span>:~$ docker --version</span><br><span class="line">Docker version 17.10.0-ce, build f4ffd25</span><br></pre></td></tr></table></figure>

<p>连接到主机之后你就可以在其上使用 Docker 了。</p>
<p><strong>官方支持驱动</strong></p>
<p>通过 <code>-d</code> 选项可以选择支持的驱动类型。</p>
<ul>
<li>amazonec2</li>
<li>azure</li>
<li>digitalocean</li>
<li>exoscale</li>
<li>generic</li>
<li>google</li>
<li>hyperv</li>
<li>none</li>
<li>openstack</li>
<li>rackspace</li>
<li>softlayer</li>
<li>virtualbox</li>
<li>vmwarevcloudair</li>
<li>vmwarefusion</li>
<li>vmwarevsphere</li>
</ul>
<p><strong>第三方驱动</strong></p>
<p>请到 <a href="https://github.com/docker/docker.github.io/blob/master/machine/AVAILABLE_DRIVER_PLUGINS.md" target="_blank" rel="noopener">第三方驱动列表</a>查看</p>
<p><strong>操作命令</strong></p>
<ul>
<li><code>active</code>                查看活跃的 Docker 主机</li>
<li><code>config</code>                输出连接的配置信息</li>
<li><code>create</code>                创建一个 Docker 主机</li>
<li><code>env</code>                   显示连接到某个主机需要的环境变量</li>
<li><code>inspect</code>               输出主机更多信息</li>
<li><code>ip</code>                    获取主机地址</li>
<li><code>kill</code>                  停止某个主机</li>
<li><code>ls</code>                    列出所有管理的主机</li>
<li><code>provision</code>             重新设置一个已存在的主机</li>
<li><code>regenerate-certs</code>      为某个主机重新生成 TLS 认证信息</li>
<li><code>restart</code>               重启主机</li>
<li><code>rm</code>                    删除某台主机</li>
<li><code>ssh</code>                   SSH 到主机上执行命令</li>
<li><code>scp</code>                   在主机之间复制文件</li>
<li><code>mount</code>                 挂载主机目录到本地</li>
<li><code>start</code>                 启动一个主机</li>
<li><code>status</code>                查看主机状态</li>
<li><code>stop</code>                  停止一个主机</li>
<li><code>upgrade</code>               更新主机 Docker 版本为最新</li>
<li><code>url</code>                   获取主机的 URL</li>
<li><code>version</code>               输出 docker-machine 版本信息</li>
<li><code>help</code>                  输出帮助信息</li>
</ul>
<p>每个命令，又带有不同的参数，可以通过</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-machine COMMAND --<span class="built_in">help</span></span><br></pre></td></tr></table></figure>

<p>来查看具体的用法。</p>
<h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><h3 id="Docker-资源链接"><a href="#Docker-资源链接" class="headerlink" title="Docker 资源链接"></a>Docker 资源链接</h3><p><strong>官方网站</strong></p>
<ul>
<li>Docker 官方主页：<a href="https://www.docker.com" target="_blank" rel="noopener">https://www.docker.com</a></li>
<li>Docker 官方博客：<a href="https://blog.docker.com/" target="_blank" rel="noopener">https://blog.docker.com/</a></li>
<li>Docker 官方文档：<a href="https://docs.docker.com/" target="_blank" rel="noopener">https://docs.docker.com/</a></li>
<li>Docker Store：<a href="https://store.docker.com" target="_blank" rel="noopener">https://store.docker.com</a></li>
<li>Docker Cloud：<a href="https://cloud.docker.com" target="_blank" rel="noopener">https://cloud.docker.com</a></li>
<li>Docker Hub：<a href="https://hub.docker.com" target="_blank" rel="noopener">https://hub.docker.com</a></li>
<li>Docker 的源代码仓库：<a href="https://github.com/moby/moby" target="_blank" rel="noopener">https://github.com/moby/moby</a></li>
<li>Docker 发布版本历史：<a href="https://docs.docker.com/release-notes/" target="_blank" rel="noopener">https://docs.docker.com/release-notes/</a></li>
<li>Docker 常见问题：<a href="https://docs.docker.com/engine/faq/" target="_blank" rel="noopener">https://docs.docker.com/engine/faq/</a></li>
<li>Docker 远端应用 API：<a href="https://docs.docker.com/develop/sdk/" target="_blank" rel="noopener">https://docs.docker.com/develop/sdk/</a></li>
</ul>
<p><strong>实践参考</strong></p>
<ul>
<li>Dockerfile 参考：<a href="https://docs.docker.com/engine/reference/builder/" target="_blank" rel="noopener">https://docs.docker.com/engine/reference/builder/</a></li>
<li>Dockerfile 最佳实践：<a href="https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/" target="_blank" rel="noopener">https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/</a></li>
</ul>
<p><strong>技术交流</strong></p>
<ul>
<li>Docker 邮件列表： <a href="https://groups.google.com/forum/#!forum/docker-user" target="_blank" rel="noopener">https://groups.google.com/forum/#!forum/docker-user</a></li>
<li>Docker 的 IRC 频道：<a href="https://chat.freenode.net#docker" target="_blank" rel="noopener">https://chat.freenode.net#docker</a></li>
<li>Docker 的 Twitter 主页：<a href="https://twitter.com/docker" target="_blank" rel="noopener">https://twitter.com/docker</a></li>
</ul>
<p><strong>其它</strong></p>
<ul>
<li>Docker 的 StackOverflow 问答主页：<a href="https://stackoverflow.com/search?q=docker" target="_blank" rel="noopener">https://stackoverflow.com/search?q=docker</a></li>
</ul>

    

    
    <div class="page-reward">
      <a href="javascript:void(0);" class="page-reward-btn tooltip-top" target="_self">
        <div class="tooltip tooltip-east">
          <span class="tooltip-item">
            赏
          </span>
          <span class="tooltip-content">
            <span class="tooltip-text">
              <span class="tooltip-inner">
                <p class="reward-p"><i class="icon icon-quo-left"></i>谢谢你请我吃糖果<i
                    class="icon icon-quo-right"></i></p>
                <div class="reward-box">
                  
                  <div class="reward-box-item">
                    <img class="reward-img" src="/myblog/img/alipay.png">
                    <span class="reward-type">支付宝</span>
                  </div>
                  
                  
                  <div class="reward-box-item">
                    <img class="reward-img" src="/myblog/img/weixin.png">
                    <span class="reward-type">微信</span>
                  </div>
                  
                </div>
              </span>
            </span>
          </span>
        </div>
      </a>
    </div>
    

    
    <div class="declare"> 
      <ul class="post-copyright">
        <li>
          <strong>本文作者：</strong>
          Nie Changan
        </li>
        <li>
          <strong>本文链接：</strong>
          <a href="http://angegit.gitee.io/myblog/2020/03/30/Docker/" title="Docker" target="_blank">http://angegit.gitee.io/myblog/2020/03/30/Docker/</a>
        </li>
        
        <li>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，均采用 <a href="https://github.com/JoeyBling/hexo-theme-yilia-plus/blob/master/LICENSE" rel="external nofollow" target="_blank">MIT</a> 许可协议。转载请注明出处！
        </li>
        
      </ul>
    </div>
    

  </div>
  <div class="article-info article-info-index">
    
    
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color5">Java</a>
        		</li>
      		
		</ul>
	</div>

    

    

    
    
<div class="share-btn share-icons tooltip-left">
  <div class="tooltip tooltip-east">
    <span class="tooltip-item">
      <a href="javascript:;" class="share-sns share-outer">
        <i class="icon icon-share"></i>
      </a>
    </span>
    <span class="tooltip-content">
      <div class="share-wrap">
        <div class="share-icons">
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="icon icon-weibo"></i>
          </a>
          <!-- <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="icon icon-weixin"></i>
          </a> -->
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="icon icon-qq"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="icon icon-douban"></i>
          </a>
          <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a>
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="icon icon-facebook"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="icon icon-twitter"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="icon icon-google"></i>
          </a>
        </div>
      </div>
    </span>
  </div>
</div>

<div class="page-modal wx-share js-wx-box">
    <a class="close js-modal-close" href="javascript:;"><i class="icon icon-close"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
     <!-- <img src="//pan.baidu.com/share/qrcode?url=http://angegit.gitee.io/2020/03/30/Docker/" alt="微信分享二维码"> -->
    </div>
</div>

<div class="mask js-mask"></div>

    
    <div class="clearfix"></div>
  </div>
  </div>
</article>


<nav id="article-nav">
  
    <a href="/myblog/2020/03/30/GC%E8%B0%83%E4%BC%98/" id="article-nav-newer" class="article-nav-link-wrap">
      <i class="icon-circle-left"></i>
      <div class="article-nav-title">
        
          GC调优
        
      </div>
    </a>
  
  
    <a href="/myblog/2020/03/30/Redis/" id="article-nav-older" class="article-nav-link-wrap">
      <div class="article-nav-title">分布式缓存数据库Redis</div>
      <i class="icon-circle-right"></i>
    </a>
  
</nav>


<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>














          </div>
        </div>
      </div>
      <footer id="footer">
  <div class="outer">
    <div id="footer-info">
    	<div class="footer-left">
    		&copy; 2019-2020 <a href="http://angegit.gitee.io/myblog/" target="_blank">Nie Changan</a>
    	</div>
      	<div class="footer-right">
			
			
      		GitHub:<a href="https://github.com/JoeyBling/hexo-theme-yilia-plus" target="_blank">hexo-theme-yilia-plus</a> by Litten
      	</div>
    </div>
  </div>
  
  
	<script src="/myblog/lib/busuanzi.pure.js"></script>
	
  
  
	
	<span id="busuanzi_container_site_pv" style="display:none">
		本站总访问量<span id="busuanzi_value_site_pv"></span>次	
	        <span class="post-meta-divider" >|</span>
	</span>
  	<span id="busuanzi_container_site_uv" style='display:none'>
  		本站访客数<span id="busuanzi_value_site_uv"></span>人
  	</span>
  
</footer>

    </div>
    <script>
	var yiliaConfig = {
		mathjax: false,
		isHome: false,
		isPost: true,
		isArchive: false,
		isTag: false,
		isCategory: false,
		open_in_new: true,
		toc_hide_index: true,
		root: "/myblog/",
		innerArchive: true,
		showTags: true
	}
</script>

<script>!function(r){function e(t){if(i[t])return i[t].exports;var n=i[t]={exports:{},id:t,loaded:!1};return r[t].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var i={};e.m=r,e.c=i,e.p="./",e(0)}([function(t,n,r){r(208),t.exports=r(205)},function(t,n,r){var d=r(3),y=r(46),g=r(26),b=r(27),x=r(47),m="prototype",S=function(t,n,r){var e,i,o,u,c=t&S.F,f=t&S.G,a=t&S.S,s=t&S.P,l=t&S.B,h=f?d:a?d[n]||(d[n]={}):(d[n]||{})[m],v=f?y:y[n]||(y[n]={}),p=v[m]||(v[m]={});for(e in f&&(r=n),r)o=((i=!c&&h&&void 0!==h[e])?h:r)[e],u=l&&i?x(o,d):s&&"function"==typeof o?x(Function.call,o):o,h&&b(h,e,o,t&S.U),v[e]!=o&&g(v,e,u),s&&p[e]!=o&&(p[e]=o)};d.core=y,S.F=1,S.G=2,S.S=4,S.P=8,S.B=16,S.W=32,S.U=64,S.R=128,t.exports=S},function(t,n,r){var e=r(5);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n,r){var e=r(118)("wks"),i=r(79),o=r(3).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n,r){var e=r(49),i=Math.min;t.exports=function(t){return 0<t?i(e(t),9007199254740991):0}},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n,r){t.exports=!r(4)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(2),i=r(174),o=r(53),u=Object.defineProperty;n.f=r(10)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){t.exports=!r(20)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(14),i=r(24);t.exports=r(12)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var e=r(22),i=r(60),o=r(42),u=Object.defineProperty;n.f=r(12)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){var e=r(96),i=r(34);t.exports=function(t){return e(i(t))}},function(t,n,r){var e=r(40)("wks"),i=r(25),o=r(6).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n,r){var e=r(51);t.exports=function(t){return Object(e(t))}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n){var r=t.exports={version:"2.6.9"};"number"==typeof __e&&(__e=r)},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,r){var e=r(18);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){t.exports=!0},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n,r){var e=r(11),i=r(75);t.exports=r(10)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var o=r(3),u=r(26),c=r(30),f=r(79)("src"),e=r(219),i="toString",a=(""+e).split(i);r(46).inspectSource=function(t){return e.call(t)},(t.exports=function(t,n,r,e){var i="function"==typeof r;i&&(c(r,"name")||u(r,"name",n)),t[n]!==r&&(i&&(c(r,f)||u(r,f,t[n]?""+t[n]:a.join(String(n)))),t===o?t[n]=r:e?t[n]?t[n]=r:u(t,n,r):(delete t[n],u(t,n,r)))})(Function.prototype,i,function(){return"function"==typeof this&&this[f]||e.call(this)})},function(t,n,r){var e=r(1),i=r(4),u=r(51),c=/"/g,o=function(t,n,r,e){var i=String(u(t)),o="<"+n;return""!==r&&(o+=" "+r+'="'+String(e).replace(c,"&quot;")+'"'),o+">"+i+"</"+n+">"};t.exports=function(n,t){var r={};r[n]=t(o),e(e.P+e.F*i(function(){var t=""[n]('"');return t!==t.toLowerCase()||3<t.split('"').length}),"String",r)}},function(t,n,r){var e=r(65),i=r(35);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n,r){var e=r(117),i=r(75),o=r(33),u=r(53),c=r(30),f=r(174),a=Object.getOwnPropertyDescriptor;n.f=r(10)?a:function(t,n){if(t=o(t),n=u(n,!0),f)try{return a(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(30),i=r(17),o=r(154)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n,r){var e=r(116),i=r(51);t.exports=function(t){return e(i(t))}},function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on  "+t);return t}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n){t.exports={}},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(14).f,i=r(9),o=r(16)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var e=r(40)("keys"),i=r(25);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n,r){var e=r(19),i=r(6),o="__core-js_shared__",u=i[o]||(i[o]={});(t.exports=function(t,n){return u[t]||(u[t]=void 0!==n?n:{})})("versions",[]).push({version:e.version,mode:r(23)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(0<t?e:r)(t)}},function(t,n,r){var i=r(18);t.exports=function(t,n){if(!i(t))return t;var r,e;if(n&&"function"==typeof(r=t.toString)&&!i(e=r.call(t)))return e;if("function"==typeof(r=t.valueOf)&&!i(e=r.call(t)))return e;if(!n&&"function"==typeof(r=t.toString)&&!i(e=r.call(t)))return e;throw TypeError("Can't convert object to primitive value")}},function(t,n,r){var e=r(6),i=r(19),o=r(23),u=r(44),c=r(14).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){n.f=r(16)},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n){var r=t.exports={version:"2.6.9"};"number"==typeof __e&&(__e=r)},function(t,n,r){var o=r(21);t.exports=function(e,i,t){if(o(e),void 0===i)return e;switch(t){case 1:return function(t){return e.call(i,t)};case 2:return function(t,n){return e.call(i,t,n)};case 3:return function(t,n,r){return e.call(i,t,n,r)}}return function(){return e.apply(i,arguments)}}},function(t,n,r){"use strict";var e=r(4);t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(0<t?e:r)(t)}},function(t,n,r){var x=r(47),m=r(116),S=r(17),w=r(8),e=r(138);t.exports=function(l,t){var h=1==l,v=2==l,p=3==l,d=4==l,y=6==l,g=5==l||y,b=t||e;return function(t,n,r){for(var e,i,o=S(t),u=m(o),c=x(n,r,3),f=w(u.length),a=0,s=h?b(t,f):v?b(t,0):void 0;a<f;a++)if((g||a in u)&&(i=c(e=u[a],a,o),l))if(h)s[a]=i;else if(i)switch(l){case 3:return!0;case 5:return e;case 6:return a;case 2:s.push(e)}else if(d)return!1;return y?-1:p||d?d:s}}},function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on  "+t);return t}},function(t,n,r){var i=r(1),o=r(46),u=r(4);t.exports=function(t,n){var r=(o.Object||{})[t]||Object[t],e={};e[t]=n(r),i(i.S+i.F*u(function(){r(1)}),"Object",e)}},function(t,n,r){var i=r(5);t.exports=function(t,n){if(!i(t))return t;var r,e;if(n&&"function"==typeof(r=t.toString)&&!i(e=r.call(t)))return e;if("function"==typeof(r=t.valueOf)&&!i(e=r.call(t)))return e;if(!n&&"function"==typeof(r=t.toString)&&!i(e=r.call(t)))return e;throw TypeError("Can't convert object to primitive value")}},function(t,n,r){var d=r(6),y=r(19),g=r(93),b=r(13),x=r(9),m="prototype",S=function(t,n,r){var e,i,o,u=t&S.F,c=t&S.G,f=t&S.S,a=t&S.P,s=t&S.B,l=t&S.W,h=c?y:y[n]||(y[n]={}),v=h[m],p=c?d:f?d[n]:(d[n]||{})[m];for(e in c&&(r=n),r)(i=!u&&p&&void 0!==p[e])&&x(h,e)||(o=i?p[e]:r[e],h[e]=c&&"function"!=typeof p[e]?r[e]:s&&i?g(o,d):l&&p[e]==o?function(e){var t=function(t,n,r){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,n)}return new e(t,n,r)}return e.apply(this,arguments)};return t[m]=e[m],t}(o):a&&"function"==typeof o?g(Function.call,o):o,a&&((h.virtual||(h.virtual={}))[e]=o,t&S.R&&v&&!v[e]&&b(v,e,o)))};S.F=1,S.G=2,S.S=4,S.P=8,S.B=16,S.W=32,S.U=64,S.R=128,t.exports=S},function(t,n,r){var e=r(34);t.exports=function(t){return Object(e(t))}},function(t,n,r){var o=r(196),e=r(1),i=r(118)("metadata"),u=i.store||(i.store=new(r(200))),c=function(t,n,r){var e=u.get(t);if(!e){if(!r)return;u.set(t,e=new o)}var i=e.get(n);if(!i){if(!r)return;e.set(n,i=new o)}return i};t.exports={store:u,map:c,has:function(t,n,r){var e=c(n,r,!1);return void 0!==e&&e.has(t)},get:function(t,n,r){var e=c(n,r,!1);return void 0===e?void 0:e.get(t)},set:function(t,n,r,e){c(r,e,!0).set(t,n)},keys:function(t,n){var r=c(t,n,!1),e=[];return r&&r.forEach(function(t,n){e.push(n)}),e},key:function(t){return void 0===t||"symbol"==typeof t?t:String(t)},exp:function(t){e(e.S,"Reflect",t)}}},function(t,n,r){"use strict";if(r(10)){var g=r(68),b=r(3),x=r(4),m=r(1),S=r(132),e=r(159),h=r(47),w=r(70),i=r(75),_=r(26),o=r(76),u=r(49),O=r(8),E=r(194),c=r(78),f=r(53),a=r(30),M=r(81),P=r(5),v=r(17),p=r(145),j=r(72),F=r(32),A=r(73).f,d=r(161),s=r(79),l=r(7),y=r(50),I=r(120),L=r(119),N=r(162),T=r(82),k=r(125),R=r(77),C=r(137),D=r(166),G=r(11),W=r(31),U=G.f,V=W.f,B=b.RangeError,q=b.TypeError,z=b.Uint8Array,K="ArrayBuffer",H="Shared"+K,J="BYTES_PER_ELEMENT",$="prototype",Y=Array[$],X=e.ArrayBuffer,Q=e.DataView,Z=y(0),tt=y(2),nt=y(3),rt=y(4),et=y(5),it=y(6),ot=I(!0),ut=I(!1),ct=N.values,ft=N.keys,at=N.entries,st=Y.lastIndexOf,lt=Y.reduce,ht=Y.reduceRight,vt=Y.join,pt=Y.sort,dt=Y.slice,yt=Y.toString,gt=Y.toLocaleString,bt=l("iterator"),xt=l("toStringTag"),mt=s("typed_constructor"),St=s("def_constructor"),wt=S.CONSTR,_t=S.TYPED,Ot=S.VIEW,Et="Wrong length!",Mt=y(1,function(t,n){return It(L(t,t[St]),n)}),Pt=x(function(){return 1===new z(new Uint16Array([1]).buffer)[0]}),jt=!!z&&!!z[$].set&&x(function(){new z(1).set({})}),Ft=function(t,n){var r=u(t);if(r<0||r%n)throw B("Wrong offset!");return r},At=function(t){if(P(t)&&_t in t)return t;throw q(t+" is not a typed array!")},It=function(t,n){if(!(P(t)&&mt in t))throw q("It is not a typed array constructor!");return new t(n)},Lt=function(t,n){return Nt(L(t,t[St]),n)},Nt=function(t,n){for(var r=0,e=n.length,i=It(t,e);r<e;)i[r]=n[r++];return i},Tt=function(t,n,r){U(t,n,{get:function(){return this._d[r]}})},kt=function(t){var n,r,e,i,o,u,c=v(t),f=arguments.length,a=1<f?arguments[1]:void 0,s=void 0!==a,l=d(c);if(null!=l&&!p(l)){for(u=l.call(c),e=[],n=0;!(o=u.next()).done;n++)e.push(o.value);c=e}for(s&&2<f&&(a=h(a,arguments[2],2)),n=0,r=O(c.length),i=It(this,r);n<r;n++)i[n]=s?a(c[n],n):c[n];return i},Rt=function(){for(var t=0,n=arguments.length,r=It(this,n);t<n;)r[t]=arguments[t++];return r},Ct=!!z&&x(function(){gt.call(new z(1))}),Dt=function(){return gt.apply(Ct?dt.call(At(this)):At(this),arguments)},Gt={copyWithin:function(t,n){return D.call(At(this),t,n,2<arguments.length?arguments[2]:void 0)},every:function(t){return rt(At(this),t,1<arguments.length?arguments[1]:void 0)},fill:function(t){return C.apply(At(this),arguments)},filter:function(t){return Lt(this,tt(At(this),t,1<arguments.length?arguments[1]:void 0))},find:function(t){return et(At(this),t,1<arguments.length?arguments[1]:void 0)},findIndex:function(t){return it(At(this),t,1<arguments.length?arguments[1]:void 0)},forEach:function(t){Z(At(this),t,1<arguments.length?arguments[1]:void 0)},indexOf:function(t){return ut(At(this),t,1<arguments.length?arguments[1]:void 0)},includes:function(t){return ot(At(this),t,1<arguments.length?arguments[1]:void 0)},join:function(t){return vt.apply(At(this),arguments)},lastIndexOf:function(t){return st.apply(At(this),arguments)},map:function(t){return Mt(At(this),t,1<arguments.length?arguments[1]:void 0)},reduce:function(t){return lt.apply(At(this),arguments)},reduceRight:function(t){return ht.apply(At(this),arguments)},reverse:function(){for(var t,n=this,r=At(n).length,e=Math.floor(r/2),i=0;i<e;)t=n[i],n[i++]=n[--r],n[r]=t;return n},some:function(t){return nt(At(this),t,1<arguments.length?arguments[1]:void 0)},sort:function(t){return pt.call(At(this),t)},subarray:function(t,n){var r=At(this),e=r.length,i=c(t,e);return new(L(r,r[St]))(r.buffer,r.byteOffset+i*r.BYTES_PER_ELEMENT,O((void 0===n?e:c(n,e))-i))}},Wt=function(t,n){return Lt(this,dt.call(At(this),t,n))},Ut=function(t){At(this);var n=Ft(arguments[1],1),r=this.length,e=v(t),i=O(e.length),o=0;if(r<i+n)throw B(Et);for(;o<i;)this[n+o]=e[o++]},Vt={entries:function(){return at.call(At(this))},keys:function(){return ft.call(At(this))},values:function(){return ct.call(At(this))}},Bt=function(t,n){return P(t)&&t[_t]&&"symbol"!=typeof n&&n in t&&String(+n)==String(n)},qt=function(t,n){return Bt(t,n=f(n,!0))?i(2,t[n]):V(t,n)},zt=function(t,n,r){return!(Bt(t,n=f(n,!0))&&P(r)&&a(r,"value"))||a(r,"get")||a(r,"set")||r.configurable||a(r,"writable")&&!r.writable||a(r,"enumerable")&&!r.enumerable?U(t,n,r):(t[n]=r.value,t)};wt||(W.f=qt,G.f=zt),m(m.S+m.F*!wt,"Object",{getOwnPropertyDescriptor:qt,defineProperty:zt}),x(function(){yt.call({})})&&(yt=gt=function(){return vt.call(this)});var Kt=o({},Gt);o(Kt,Vt),_(Kt,bt,Vt.values),o(Kt,{slice:Wt,set:Ut,constructor:function(){},toString:yt,toLocaleString:Dt}),Tt(Kt,"buffer","b"),Tt(Kt,"byteOffset","o"),Tt(Kt,"byteLength","l"),Tt(Kt,"length","e"),U(Kt,xt,{get:function(){return this[_t]}}),t.exports=function(t,l,n,o){var h=t+((o=!!o)?"Clamped":"")+"Array",r="get"+t,u="set"+t,v=b[h],c=v||{},e=v&&F(v),i=!v||!S.ABV,f={},a=v&&v[$],p=function(t,i){U(t,i,{get:function(){return t=i,(n=this._d).v[r](t*l+n.o,Pt);var t,n},set:function(t){return n=i,r=t,e=this._d,o&&(r=(r=Math.round(r))<0?0:255<r?255:255&r),void e.v[u](n*l+e.o,r,Pt);var n,r,e},enumerable:!0})};i?(v=n(function(t,n,r,e){w(t,v,h,"_d");var i,o,u,c,f=0,a=0;if(P(n)){if(!(n instanceof X||(c=M(n))==K||c==H))return _t in n?Nt(v,n):kt.call(v,n);i=n,a=Ft(r,l);var s=n.byteLength;if(void 0===e){if(s%l)throw B(Et);if((o=s-a)<0)throw B(Et)}else if(s<(o=O(e)*l)+a)throw B(Et);u=o/l}else u=E(n),i=new X(o=u*l);for(_(t,"_d",{b:i,o:a,l:o,e:u,v:new Q(i)});f<u;)p(t,f++)}),a=v[$]=j(Kt),_(a,"constructor",v)):x(function(){v(1)})&&x(function(){new v(-1)})&&k(function(t){new v,new v(null),new v(1.5),new v(t)},!0)||(v=n(function(t,n,r,e){var i;return w(t,v,h),P(n)?n instanceof X||(i=M(n))==K||i==H?void 0!==e?new c(n,Ft(r,l),e):void 0!==r?new c(n,Ft(r,l)):new c(n):_t in n?Nt(v,n):kt.call(v,n):new c(E(n))}),Z(e!==Function.prototype?A(c).concat(A(e)):A(c),function(t){t in v||_(v,t,c[t])}),v[$]=a,g||(a.constructor=v));var s=a[bt],d=!!s&&("values"==s.name||null==s.name),y=Vt.values;_(v,mt,!0),_(a,_t,h),_(a,Ot,!0),_(a,St,v),(o?new v(1)[xt]==h:xt in a)||U(a,xt,{get:function(){return h}}),f[h]=v,m(m.G+m.W+m.F*(v!=c),f),m(m.S,h,{BYTES_PER_ELEMENT:l}),m(m.S+m.F*x(function(){c.of.call(v,1)}),h,{from:kt,of:Rt}),J in a||_(a,J,l),m(m.P,h,Gt),R(h),m(m.P+m.F*jt,h,{set:Ut}),m(m.P+m.F*!d,h,Vt),g||a.toString==yt||(a.toString=yt),m(m.P+m.F*x(function(){new v(1).slice()}),h,{slice:Wt}),m(m.P+m.F*(x(function(){return[1,2].toLocaleString()!=new v([1,2]).toLocaleString()})||!x(function(){a.toLocaleString.call([1,2])})),h,{toLocaleString:Dt}),T[h]=d?s:y,g||d||_(a,bt,y)}}else t.exports=function(){}},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n,r){var e=r(18),i=r(6).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,r){t.exports=!r(12)&&!r(20)(function(){return 7!=Object.defineProperty(r(59)("div"),"a",{get:function(){return 7}}).a})},function(t,n,r){"use strict";var x=r(23),m=r(54),S=r(66),w=r(13),_=r(36),O=r(98),E=r(38),M=r(104),P=r(16)("iterator"),j=!([].keys&&"next"in[].keys()),F="values",A=function(){return this};t.exports=function(t,n,r,e,i,o,u){O(r,n,e);var c,f,a,s=function(t){if(!j&&t in p)return p[t];switch(t){case"keys":case F:return function(){return new r(this,t)}}return function(){return new r(this,t)}},l=n+" Iterator",h=i==F,v=!1,p=t.prototype,d=p[P]||p["@@iterator"]||i&&p[i],y=d||s(i),g=i?h?s("entries"):y:void 0,b="Array"==n&&p.entries||d;if(b&&((a=M(b.call(new t)))!==Object.prototype&&a.next&&(E(a,l,!0),x||"function"==typeof a[P]||w(a,P,A))),h&&d&&d.name!==F&&(v=!0,y=function(){return d.call(this)}),x&&!u||!j&&!v&&p[P]||w(p,P,y),_[n]=y,_[l]=A,i)if(c={values:h?y:s(F),keys:o?y:s("keys"),entries:g},u)for(f in c)f in p||S(p,f,c[f]);else m(m.P+m.F*(j||v),n,c);return c}},function(t,n,e){var i=e(22),o=e(101),u=e(35),c=e(39)("IE_PROTO"),f=function(){},a="prototype",s=function(){var t,n=e(59)("iframe"),r=u.length;for(n.style.display="none",e(95).appendChild(n),n.src="javascript:",(t=n.contentWindow.document).open(),t.write("<script>document.F=Object<\/script>"),t.close(),s=t.F;r--;)delete s[a][u[r]];return s()};t.exports=Object.create||function(t,n){var r;return null!==t?(f[a]=i(t),r=new f,f[a]=null,r[c]=t):r=s(),void 0===n?r:o(r,n)}},function(t,n,r){var e=r(65),i=r(35).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){var u=r(9),c=r(15),f=r(92)(!1),a=r(39)("IE_PROTO");t.exports=function(t,n){var r,e=c(t),i=0,o=[];for(r in e)r!=a&&u(e,r)&&o.push(r);for(;n.length>i;)u(e,r=n[i++])&&(~f(o,r)||o.push(r));return o}},function(t,n,r){t.exports=r(13)},function(t,n,r){var e=r(7)("unscopables"),i=Array.prototype;null==i[e]&&r(26)(i,e,{}),t.exports=function(t){i[e][t]=!0}},function(t,n){t.exports=!1},function(t,n,r){var e=r(79)("meta"),i=r(5),o=r(30),u=r(11).f,c=0,f=Object.isExtensible||function(){return!0},a=!r(4)(function(){return f(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=t.exports={KEY:e,NEED:!1,fastKey:function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!f(t))return"F";if(!n)return"E";s(t)}return t[e].i},getWeak:function(t,n){if(!o(t,e)){if(!f(t))return!0;if(!n)return!1;s(t)}return t[e].w},onFreeze:function(t){return a&&l.NEED&&f(t)&&!o(t,e)&&s(t),t}}},function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},function(t,n,r){var h=r(47),v=r(177),p=r(145),d=r(2),y=r(8),g=r(161),b={},x={};(n=t.exports=function(t,n,r,e,i){var o,u,c,f,a=i?function(){return t}:g(t),s=h(r,e,n?2:1),l=0;if("function"!=typeof a)throw TypeError(t+" is not iterable!");if(p(a)){for(o=y(t.length);l<o;l++)if((f=n?s(d(u=t[l])[0],u[1]):s(t[l]))===b||f===x)return f}else for(c=a.call(t);!(u=c.next()).done;)if((f=v(c,s,u.value,n))===b||f===x)return f}).BREAK=b,n.RETURN=x},function(t,n,e){var i=e(2),o=e(183),u=e(141),c=e(154)("IE_PROTO"),f=function(){},a="prototype",s=function(){var t,n=e(140)("iframe"),r=u.length;for(n.style.display="none",e(143).appendChild(n),n.src="javascript:",(t=n.contentWindow.document).open(),t.write("<script>document.F=Object<\/script>"),t.close(),s=t.F;r--;)delete s[a][u[r]];return s()};t.exports=Object.create||function(t,n){var r;return null!==t?(f[a]=i(t),r=new f,f[a]=null,r[c]=t):r=s(),void 0===n?r:o(r,n)}},function(t,n,r){var e=r(185),i=r(141).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n,r){var e=r(185),i=r(141);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n,r){var i=r(27);t.exports=function(t,n,r){for(var e in n)i(t,e,n[e],r);return t}},function(t,n,r){"use strict";var e=r(3),i=r(11),o=r(10),u=r(7)("species");t.exports=function(t){var n=e[t];o&&n&&!n[u]&&i.f(n,u,{configurable:!0,get:function(){return this}})}},function(t,n,r){var e=r(49),i=Math.max,o=Math.min;t.exports=function(t,n){return(t=e(t))<0?i(t+n,0):o(t,n)}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n,r){var e=r(5);t.exports=function(t,n){if(!e(t)||t._t!==n)throw TypeError("Incompatible receiver, "+n+" required!");return t}},function(t,n,r){var i=r(45),o=r(7)("toStringTag"),u="Arguments"==i(function(){return arguments}());t.exports=function(t){var n,r,e;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=function(t,n){try{return t[n]}catch(t){}}(n=Object(t),o))?r:u?i(n):"Object"==(e=i(n))&&"function"==typeof n.callee?"Arguments":e}},function(t,n){t.exports={}},function(t,n,r){var e=r(11).f,i=r(30),o=r(7)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var u=r(1),e=r(51),c=r(4),f=r(157),i="["+f+"]",o=RegExp("^"+i+i+"*"),a=RegExp(i+i+"*$"),s=function(t,n,r){var e={},i=c(function(){return!!f[t]()||"​"!="​"[t]()}),o=e[t]=i?n(l):f[t];r&&(e[r]=o),u(u.P+u.F*i,"String",e)},l=s.trim=function(t,n){return t=String(e(t)),1&n&&(t=t.replace(o,"")),2&n&&(t=t.replace(a,"")),t};t.exports=s},function(t,n,r){t.exports={default:r(88),__esModule:!0}},function(t,n,r){t.exports={default:r(89),__esModule:!0}},function(t,n,r){"use strict";function e(t){return t&&t.__esModule?t:{default:t}}n.__esModule=!0;var i=e(r(86)),o=e(r(85)),u="function"==typeof o.default&&"symbol"==typeof i.default?function(t){return typeof t}:function(t){return t&&"function"==typeof o.default&&t.constructor===o.default&&t!==o.default.prototype?"symbol":typeof t};n.default="function"==typeof o.default&&"symbol"===u(i.default)?function(t){return void 0===t?"undefined":u(t)}:function(t){return t&&"function"==typeof o.default&&t.constructor===o.default&&t!==o.default.prototype?"symbol":void 0===t?"undefined":u(t)}},function(t,n,r){r(111),r(109),r(112),r(113),t.exports=r(19).Symbol},function(t,n,r){r(110),r(114),t.exports=r(44).f("iterator")},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n){t.exports=function(){}},function(t,n,r){var f=r(15),a=r(107),s=r(106);t.exports=function(c){return function(t,n,r){var e,i=f(t),o=a(i.length),u=s(r,o);if(c&&n!=n){for(;u<o;)if((e=i[u++])!=e)return!0}else for(;u<o;u++)if((c||u in i)&&i[u]===n)return c||u||0;return!c&&-1}}},function(t,n,r){var o=r(90);t.exports=function(e,i,t){if(o(e),void 0===i)return e;switch(t){case 1:return function(t){return e.call(i,t)};case 2:return function(t,n){return e.call(i,t,n)};case 3:return function(t,n,r){return e.call(i,t,n,r)}}return function(){return e.apply(i,arguments)}}},function(t,n,r){var c=r(29),f=r(64),a=r(37);t.exports=function(t){var n=c(t),r=f.f;if(r)for(var e,i=r(t),o=a.f,u=0;i.length>u;)o.call(t,e=i[u++])&&n.push(e);return n}},function(t,n,r){var e=r(6).document;t.exports=e&&e.documentElement},function(t,n,r){var e=r(58);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n,r){var e=r(58);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){"use strict";var e=r(62),i=r(24),o=r(38),u={};r(13)(u,r(16)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n,r){var e=r(25)("meta"),i=r(18),o=r(9),u=r(14).f,c=0,f=Object.isExtensible||function(){return!0},a=!r(20)(function(){return f(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=t.exports={KEY:e,NEED:!1,fastKey:function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!f(t))return"F";if(!n)return"E";s(t)}return t[e].i},getWeak:function(t,n){if(!o(t,e)){if(!f(t))return!0;if(!n)return!1;s(t)}return t[e].w},onFreeze:function(t){return a&&l.NEED&&f(t)&&!o(t,e)&&s(t),t}}},function(t,n,r){var u=r(14),c=r(22),f=r(29);t.exports=r(12)?Object.defineProperties:function(t,n){c(t);for(var r,e=f(n),i=e.length,o=0;o<i;)u.f(t,r=e[o++],n[r]);return t}},function(t,n,r){var e=r(37),i=r(24),o=r(15),u=r(42),c=r(9),f=r(60),a=Object.getOwnPropertyDescriptor;n.f=r(12)?a:function(t,n){if(t=o(t),n=u(n,!0),f)try{return a(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(15),i=r(63).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?function(t){try{return i(t)}catch(t){return u.slice()}}(t):i(e(t))}},function(t,n,r){var e=r(9),i=r(55),o=r(39)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n,r){var f=r(41),a=r(34);t.exports=function(c){return function(t,n){var r,e,i=String(a(t)),o=f(n),u=i.length;return o<0||u<=o?c?"":void 0:(r=i.charCodeAt(o))<55296||56319<r||o+1===u||(e=i.charCodeAt(o+1))<56320||57343<e?c?i.charAt(o):r:c?i.slice(o,o+2):e-56320+(r-55296<<10)+65536}}},function(t,n,r){var e=r(41),i=Math.max,o=Math.min;t.exports=function(t,n){return(t=e(t))<0?i(t+n,0):o(t,n)}},function(t,n,r){var e=r(41),i=Math.min;t.exports=function(t){return 0<t?i(e(t),9007199254740991):0}},function(t,n,r){"use strict";var e=r(91),i=r(99),o=r(36),u=r(15);t.exports=r(61)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):i(0,"keys"==n?r:"values"==n?t[r]:[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n){},function(t,n,r){"use strict";var e=r(105)(!0);r(61)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";var e=r(6),u=r(9),i=r(12),o=r(54),c=r(66),f=r(100).KEY,a=r(20),s=r(40),l=r(38),h=r(25),v=r(16),p=r(44),d=r(43),y=r(94),g=r(97),b=r(22),x=r(18),m=r(55),S=r(15),w=r(42),_=r(24),O=r(62),E=r(103),M=r(102),P=r(64),j=r(14),F=r(29),A=M.f,I=j.f,L=E.f,N=e.Symbol,T=e.JSON,k=T&&T.stringify,R="prototype",C=v("_hidden"),D=v("toPrimitive"),G={}.propertyIsEnumerable,W=s("symbol-registry"),U=s("symbols"),V=s("op-symbols"),B=Object[R],q="function"==typeof N&&!!P.f,z=e.QObject,K=!z||!z[R]||!z[R].findChild,H=i&&a(function(){return 7!=O(I({},"a",{get:function(){return I(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=A(B,n);e&&delete B[n],I(t,n,r),e&&t!==B&&I(B,n,e)}:I,J=function(t){var n=U[t]=O(N[R]);return n._k=t,n},$=q&&"symbol"==typeof N.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof N},Y=function(t,n,r){return t===B&&Y(V,n,r),b(t),n=w(n,!0),b(r),u(U,n)?(r.enumerable?(u(t,C)&&t[C][n]&&(t[C][n]=!1),r=O(r,{enumerable:_(0,!1)})):(u(t,C)||I(t,C,_(1,{})),t[C][n]=!0),H(t,n,r)):I(t,n,r)},X=function(t,n){b(t);for(var r,e=y(n=S(n)),i=0,o=e.length;i<o;)Y(t,r=e[i++],n[r]);return t},Q=function(t){var n=G.call(this,t=w(t,!0));return!(this===B&&u(U,t)&&!u(V,t))&&(!(n||!u(this,t)||!u(U,t)||u(this,C)&&this[C][t])||n)},Z=function(t,n){if(t=S(t),n=w(n,!0),t!==B||!u(U,n)||u(V,n)){var r=A(t,n);return!r||!u(U,n)||u(t,C)&&t[C][n]||(r.enumerable=!0),r}},tt=function(t){for(var n,r=L(S(t)),e=[],i=0;r.length>i;)u(U,n=r[i++])||n==C||n==f||e.push(n);return e},nt=function(t){for(var n,r=t===B,e=L(r?V:S(t)),i=[],o=0;e.length>o;)!u(U,n=e[o++])||r&&!u(B,n)||i.push(U[n]);return i};q||(c((N=function(){if(this instanceof N)throw TypeError("Symbol is not a constructor!");var n=h(0<arguments.length?arguments[0]:void 0),r=function(t){this===B&&r.call(V,t),u(this,C)&&u(this[C],n)&&(this[C][n]=!1),H(this,n,_(1,t))};return i&&K&&H(B,n,{configurable:!0,set:r}),J(n)})[R],"toString",function(){return this._k}),M.f=Z,j.f=Y,r(63).f=E.f=tt,r(37).f=Q,P.f=nt,i&&!r(23)&&c(B,"propertyIsEnumerable",Q,!0),p.f=function(t){return J(v(t))}),o(o.G+o.W+o.F*!q,{Symbol:N});for(var rt="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),et=0;rt.length>et;)v(rt[et++]);for(var it=F(v.store),ot=0;it.length>ot;)d(it[ot++]);o(o.S+o.F*!q,"Symbol",{for:function(t){return u(W,t+="")?W[t]:W[t]=N(t)},keyFor:function(t){if(!$(t))throw TypeError(t+" is not a symbol!");for(var n in W)if(W[n]===t)return n},useSetter:function(){K=!0},useSimple:function(){K=!1}}),o(o.S+o.F*!q,"Object",{create:function(t,n){return void 0===n?O(t):X(O(t),n)},defineProperty:Y,defineProperties:X,getOwnPropertyDescriptor:Z,getOwnPropertyNames:tt,getOwnPropertySymbols:nt});var ut=a(function(){P.f(1)});o(o.S+o.F*ut,"Object",{getOwnPropertySymbols:function(t){return P.f(m(t))}}),T&&o(o.S+o.F*(!q||a(function(){var t=N();return"[null]"!=k([t])||"{}"!=k({a:t})||"{}"!=k(Object(t))})),"JSON",{stringify:function(t){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);if(r=n=e[1],(x(n)||void 0!==t)&&!$(t))return g(n)||(n=function(t,n){if("function"==typeof r&&(n=r.call(this,t,n)),!$(n))return n}),e[1]=n,k.apply(T,e)}}),N[R][D]||r(13)(N[R],D,N[R].valueOf),l(N,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){r(43)("asyncIterator")},function(t,n,r){r(43)("observable")},function(t,n,r){r(108);for(var e=r(6),i=r(13),o=r(36),u=r(16)("toStringTag"),c="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),f=0;f<c.length;f++){var a=c[f],s=e[a],l=s&&s.prototype;l&&!l[u]&&i(l,u,a),o[a]=o.Array}},function(t,n,r){"use strict";var e=r(2);t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},function(t,n,r){var e=r(45);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(46),i=r(3),o="__core-js_shared__",u=i[o]||(i[o]={});(t.exports=function(t,n){return u[t]||(u[t]=void 0!==n?n:{})})("versions",[]).push({version:e.version,mode:r(68)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(t,n,r){var i=r(2),o=r(21),u=r(7)("species");t.exports=function(t,n){var r,e=i(t).constructor;return void 0===e||null==(r=i(e)[u])?n:o(r)}},function(t,n,r){var f=r(33),a=r(8),s=r(78);t.exports=function(c){return function(t,n,r){var e,i=f(t),o=a(i.length),u=s(r,o);if(c&&n!=n){for(;u<o;)if((e=i[u++])!=e)return!0}else for(;u<o;u++)if((c||u in i)&&i[u]===n)return c||u||0;return!c&&-1}}},function(t,n,r){"use strict";var g=r(3),b=r(1),x=r(27),m=r(76),S=r(69),w=r(71),_=r(70),O=r(5),E=r(4),M=r(125),P=r(83),j=r(144);t.exports=function(e,t,n,r,i,o){var u=g[e],c=u,f=i?"set":"add",a=c&&c.prototype,s={},l=function(t){var r=a[t];x(a,t,"delete"==t?function(t){return!(o&&!O(t))&&r.call(this,0===t?0:t)}:"has"==t?function(t){return!(o&&!O(t))&&r.call(this,0===t?0:t)}:"get"==t?function(t){return o&&!O(t)?void 0:r.call(this,0===t?0:t)}:"add"==t?function(t){return r.call(this,0===t?0:t),this}:function(t,n){return r.call(this,0===t?0:t,n),this})};if("function"==typeof c&&(o||a.forEach&&!E(function(){(new c).entries().next()}))){var h=new c,v=h[f](o?{}:-0,1)!=h,p=E(function(){h.has(1)}),d=M(function(t){new c(t)}),y=!o&&E(function(){for(var t=new c,n=5;n--;)t[f](n,n);return!t.has(-0)});d||(((c=t(function(t,n){_(t,c,e);var r=j(new u,t,c);return null!=n&&w(n,i,r[f],r),r})).prototype=a).constructor=c),(p||y)&&(l("delete"),l("has"),i&&l("get")),(y||v)&&l(f),o&&a.clear&&delete a.clear}else c=r.getConstructor(t,e,i,f),m(c.prototype,n),S.NEED=!0;return P(c,e),s[e]=c,b(b.G+b.W+b.F*(c!=u),s),o||r.setStrong(c,e,i),c}},function(t,n,r){"use strict";r(197);var s=r(27),l=r(26),h=r(4),v=r(51),p=r(7),d=r(152),y=p("species"),g=!h(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$<a>")}),b=function(){var t=/(?:)/,n=t.exec;t.exec=function(){return n.apply(this,arguments)};var r="ab".split(t);return 2===r.length&&"a"===r[0]&&"b"===r[1]}();t.exports=function(r,t,n){var e=p(r),o=!h(function(){var t={};return t[e]=function(){return 7},7!=""[r](t)}),i=o?!h(function(){var t=!1,n=/a/;return n.exec=function(){return t=!0,null},"split"===r&&(n.constructor={},n.constructor[y]=function(){return n}),n[e](""),!t}):void 0;if(!o||!i||"replace"===r&&!g||"split"===r&&!b){var u=/./[e],c=n(v,e,""[r],function(t,n,r,e,i){return n.exec===d?o&&!i?{done:!0,value:u.call(n,r,e)}:{done:!0,value:t.call(r,n,e)}:{done:!1}}),f=c[0],a=c[1];s(String.prototype,r,f),l(RegExp.prototype,e,2==t?function(t,n){return a.call(t,this,n)}:function(t){return a.call(t,this)})}}},function(t,n,r){var e=r(45);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){var e=r(5),i=r(45),o=r(7)("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[o])?!!n:"RegExp"==i(t))}},function(t,n,r){var o=r(7)("iterator"),u=!1;try{var e=[7][o]();e.return=function(){u=!0},Array.from(e,function(){throw 2})}catch(t){}t.exports=function(t,n){if(!n&&!u)return!1;var r=!1;try{var e=[7],i=e[o]();i.next=function(){return{done:r=!0}},e[o]=function(){return i},t(e)}catch(t){}return r}},function(t,n,r){"use strict";t.exports=r(68)||!r(4)(function(){var t=Math.random();__defineSetter__.call(null,t,function(){}),delete r(3)[t]})},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){"use strict";var i=r(81),o=RegExp.prototype.exec;t.exports=function(t,n){var r=t.exec;if("function"==typeof r){var e=r.call(t,n);if("object"!=typeof e)throw new TypeError("RegExp exec method returned something other than an Object or null");return e}if("RegExp"!==i(t))throw new TypeError("RegExp#exec called on incompatible receiver");return o.call(t,n)}},function(t,n,r){"use strict";var e=r(1),u=r(21),c=r(47),f=r(71);t.exports=function(t){e(e.S,t,{from:function(t){var n,r,e,i,o=arguments[1];return u(this),(n=void 0!==o)&&u(o),null==t?new this:(r=[],n?(e=0,i=c(o,arguments[2],2),f(t,!1,function(t){r.push(i(t,e++))})):f(t,!1,r.push,r),new this(r))}})}},function(t,n,r){"use strict";var e=r(1);t.exports=function(t){e(e.S,t,{of:function(){for(var t=arguments.length,n=new Array(t);t--;)n[t]=arguments[t];return new this(n)}})}},function(t,n,r){var f=r(49),a=r(51);t.exports=function(c){return function(t,n){var r,e,i=String(a(t)),o=f(n),u=i.length;return o<0||u<=o?c?"":void 0:(r=i.charCodeAt(o))<55296||56319<r||o+1===u||(e=i.charCodeAt(o+1))<56320||57343<e?c?i.charAt(o):r:c?i.slice(o,o+2):e-56320+(r-55296<<10)+65536}}},function(t,n,r){for(var e,i=r(3),o=r(26),u=r(79),c=u("typed_array"),f=u("view"),a=!(!i.ArrayBuffer||!i.DataView),s=a,l=0,h="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");l<9;)(e=i[h[l++]])?(o(e.prototype,c,!0),o(e.prototype,f,!0)):s=!1;t.exports={ABV:a,CONSTR:s,TYPED:c,VIEW:f}},function(t,n,r){var e=r(3).navigator;t.exports=e&&e.userAgent||""},function(t,n){"use strict";var r,e={versions:(r=window.navigator.userAgent,{trident:-1<r.indexOf("Trident"),presto:-1<r.indexOf("Presto"),webKit:-1<r.indexOf("AppleWebKit"),gecko:-1<r.indexOf("Gecko")&&-1==r.indexOf("KHTML"),mobile:!!r.match(/AppleWebKit.*Mobile.*/),ios:!!r.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),android:-1<r.indexOf("Android")||-1<r.indexOf("Linux"),iPhone:-1<r.indexOf("iPhone")||-1<r.indexOf("Mac"),iPad:-1<r.indexOf("iPad"),webApp:-1==r.indexOf("Safari"),weixin:-1==r.indexOf("MicroMessenger")})};t.exports=e},function(t,n,r){"use strict";var e,i=r(87),l=(e=i)&&e.__esModule?e:{default:e},h=function(){function n(t,n,r){return n||r?String.fromCharCode(n||r):o[t]||t}function r(t){return s[t]}var e=/&quot;|&lt;|&gt;|&amp;|&nbsp;|&apos;|&#(\d+);|&#(\d+)/g,i=/['<> "&]/g,o={"&quot;":'"',"&lt;":"<","&gt;":">","&amp;":"&","&nbsp;":" "},u=/\u00a0/g,c=/<br\s*\/?>/gi,f=/\r?\n/g,a=/\s/g,s={};for(var t in o)s[o[t]]=t;return o["&apos;"]="'",s["'"]="&#39;",{encode:function(t){return t?(""+t).replace(i,r).replace(f,"<br/>").replace(a,"&nbsp;"):""},decode:function(t){return t?(""+t).replace(c,"\n").replace(e,n).replace(u," "):""},encodeBase16:function(t){if(!t)return t;for(var n=[],r=0,e=(t+="").length;r<e;r++)n.push(t.charCodeAt(r).toString(16).toUpperCase());return n.join("")},encodeBase16forJSON:function(t){if(!t)return t;for(var n=[],r=0,e=(t=t.replace(/[\u4E00-\u9FBF]/gi,function(t){return escape(t).replace("%u","\\u")})).length;r<e;r++)n.push(t.charCodeAt(r).toString(16).toUpperCase());return n.join("")},decodeBase16:function(t){if(!t)return t;for(var n=[],r=0,e=(t+="").length;r<e;r+=2)n.push(String.fromCharCode("0x"+t.slice(r,r+2)));return n.join("")},encodeObject:function(t){if(t instanceof Array)for(var n=0,r=t.length;n<r;n++)t[n]=h.encodeObject(t[n]);else if("object"==(void 0===t?"undefined":(0,l.default)(t)))for(var e in t)t[e]=h.encodeObject(t[e]);else if("string"==typeof t)return h.encode(t);return t},loadScript:function(t){var n=document.createElement("script");document.getElementsByTagName("body")[0].appendChild(n),n.setAttribute("src",t)},addLoadEvent:function(t){var n=window.onload;"function"!=typeof window.onload?window.onload=t:window.onload=function(){n(),t()}}}}();t.exports=h},function(t,n,r){"use strict";var e=r(131)(!0);t.exports=function(t,n,r){return n+(r?e(t,n).length:1)}},function(t,n,r){"use strict";var c=r(17),f=r(78),a=r(8);t.exports=function(t){for(var n=c(this),r=a(n.length),e=arguments.length,i=f(1<e?arguments[1]:void 0,r),o=2<e?arguments[2]:void 0,u=void 0===o?r:f(o,r);i<u;)n[i++]=t;return n}},function(t,n,r){var e=r(215);t.exports=function(t,n){return new(e(t))(n)}},function(t,n,r){"use strict";var e=r(11),i=r(75);t.exports=function(t,n,r){n in t?e.f(t,n,i(0,r)):t[n]=r}},function(t,n,r){var e=r(5),i=r(3).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n,r){var e=r(7)("match");t.exports=function(n){var r=/./;try{"/./"[n](r)}catch(t){try{return r[e]=!1,!"/./"[n](r)}catch(n){}}return!0}},function(t,n,r){var e=r(3).document;t.exports=e&&e.documentElement},function(t,n,r){var o=r(5),u=r(153).set;t.exports=function(t,n,r){var e,i=n.constructor;return i!==r&&"function"==typeof i&&(e=i.prototype)!==r.prototype&&o(e)&&u&&u(t,e),t}},function(t,n,r){var e=r(82),i=r(7)("iterator"),o=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||o[i]===t)}},function(t,n,r){"use strict";var e=r(72),i=r(75),o=r(83),u={};r(26)(u,r(7)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n,r){"use strict";var x=r(68),m=r(1),S=r(27),w=r(26),_=r(82),O=r(146),E=r(83),M=r(32),P=r(7)("iterator"),j=!([].keys&&"next"in[].keys()),F="values",A=function(){return this};t.exports=function(t,n,r,e,i,o,u){O(r,n,e);var c,f,a,s=function(t){if(!j&&t in p)return p[t];switch(t){case"keys":case F:return function(){return new r(this,t)}}return function(){return new r(this,t)}},l=n+" Iterator",h=i==F,v=!1,p=t.prototype,d=p[P]||p["@@iterator"]||i&&p[i],y=d||s(i),g=i?h?s("entries"):y:void 0,b="Array"==n&&p.entries||d;if(b&&((a=M(b.call(new t)))!==Object.prototype&&a.next&&(E(a,l,!0),x||"function"==typeof a[P]||w(a,P,A))),h&&d&&d.name!==F&&(v=!0,y=function(){return d.call(this)}),x&&!u||!j&&!v&&p[P]||w(p,P,y),_[n]=y,_[l]=A,i)if(c={values:h?y:s(F),keys:o?y:s("keys"),entries:g},u)for(f in c)f in p||S(p,f,c[f]);else m(m.P+m.F*(j||v),n,c);return c}},function(t,n){var r=Math.expm1;t.exports=!r||22025.465794806718<r(10)||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:-1e-6<t&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},function(t,n,r){var c=r(3),f=r(158).set,a=c.MutationObserver||c.WebKitMutationObserver,s=c.process,l=c.Promise,h="process"==r(45)(s);t.exports=function(){var r,e,i,t=function(){var t,n;for(h&&(t=s.domain)&&t.exit();r;){n=r.fn,r=r.next;try{n()}catch(t){throw r?i():e=void 0,t}}e=void 0,t&&t.enter()};if(h)i=function(){s.nextTick(t)};else if(!a||c.navigator&&c.navigator.standalone)if(l&&l.resolve){var n=l.resolve(void 0);i=function(){n.then(t)}}else i=function(){f.call(c,t)};else{var o=!0,u=document.createTextNode("");new a(t).observe(u,{characterData:!0}),i=function(){u.data=o=!o}}return function(t){var n={fn:t,next:void 0};e&&(e.next=n),r||(r=n,i()),e=n}}},function(t,n,r){"use strict";function e(t){var r,e;this.promise=new t(function(t,n){if(void 0!==r||void 0!==e)throw TypeError("Bad Promise constructor");r=t,e=n}),this.resolve=i(r),this.reject=i(e)}var i=r(21);t.exports.f=function(t){return new e(t)}},function(t,n,r){"use strict";var e,i,u=r(115),c=RegExp.prototype.exec,f=String.prototype.replace,o=c,a="lastIndex",s=(e=/a/,i=/b*/g,c.call(e,"a"),c.call(i,"a"),0!==e[a]||0!==i[a]),l=void 0!==/()??/.exec("")[1];(s||l)&&(o=function(t){var n,r,e,i,o=this;return l&&(r=new RegExp("^"+o.source+"$(?!\\s)",u.call(o))),s&&(n=o[a]),e=c.call(o,t),s&&e&&(o[a]=o.global?e.index+e[0].length:n),l&&e&&1<e.length&&f.call(e[0],r,function(){for(i=1;i<arguments.length-2;i++)void 0===arguments[i]&&(e[i]=void 0)}),e}),t.exports=o},function(t,n,i){var r=i(5),e=i(2),o=function(t,n){if(e(t),!r(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,r,e){try{(e=i(47)(Function.call,i(31).f(Object.prototype,"__proto__").set,2))(t,[]),r=!(t instanceof Array)}catch(t){r=!0}return function(t,n){return o(t,n),r?t.__proto__=n:e(t,n),t}}({},!1):void 0),check:o}},function(t,n,r){var e=r(118)("keys"),i=r(79);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n,r){var e=r(124),i=r(51);t.exports=function(t,n,r){if(e(n))throw TypeError("String#"+r+" doesn't accept regex!");return String(i(t))}},function(t,n,r){"use strict";var i=r(49),o=r(51);t.exports=function(t){var n=String(o(this)),r="",e=i(t);if(e<0||e==1/0)throw RangeError("Count can't be negative");for(;0<e;(e>>>=1)&&(n+=n))1&e&&(r+=n);return r}},function(t,n){t.exports="\t\n\v\f\r   ᠎             　\u2028\u2029\ufeff"},function(t,n,r){var e,i,o,u=r(47),c=r(175),f=r(143),a=r(140),s=r(3),l=s.process,h=s.setImmediate,v=s.clearImmediate,p=s.MessageChannel,d=s.Dispatch,y=0,g={},b="onreadystatechange",x=function(){var t=+this;if(g.hasOwnProperty(t)){var n=g[t];delete g[t],n()}},m=function(t){x.call(t.data)};h&&v||(h=function(t){for(var n=[],r=1;arguments.length>r;)n.push(arguments[r++]);return g[++y]=function(){c("function"==typeof t?t:Function(t),n)},e(y),y},v=function(t){delete g[t]},"process"==r(45)(l)?e=function(t){l.nextTick(u(x,t,1))}:d&&d.now?e=function(t){d.now(u(x,t,1))}:p?(o=(i=new p).port2,i.port1.onmessage=m,e=u(o.postMessage,o,1)):s.addEventListener&&"function"==typeof postMessage&&!s.importScripts?(e=function(t){s.postMessage(t+"","*")},s.addEventListener("message",m,!1)):e=b in a("script")?function(t){f.appendChild(a("script"))[b]=function(){f.removeChild(this),x.call(t)}}:function(t){setTimeout(u(x,t,1),0)}),t.exports={set:h,clear:v}},function(t,n,r){"use strict";function e(t,n,r){var e,i,o,u=new Array(r),c=8*r-n-1,f=(1<<c)-1,a=f>>1,s=23===n?W(2,-24)-W(2,-77):0,l=0,h=t<0||0===t&&1/t<0?1:0;for((t=G(t))!=t||t===C?(i=t!=t?1:0,e=f):(e=U(V(t)/B),t*(o=W(2,-e))<1&&(e--,o*=2),2<=(t+=1<=e+a?s/o:s*W(2,1-a))*o&&(e++,o/=2),f<=e+a?(i=0,e=f):1<=e+a?(i=(t*o-1)*W(2,n),e+=a):(i=t*W(2,a-1)*W(2,n),e=0));8<=n;u[l++]=255&i,i/=256,n-=8);for(e=e<<n|i,c+=n;0<c;u[l++]=255&e,e/=256,c-=8);return u[--l]|=128*h,u}function i(t,n,r){var e,i=8*r-n-1,o=(1<<i)-1,u=o>>1,c=i-7,f=r-1,a=t[f--],s=127&a;for(a>>=7;0<c;s=256*s+t[f],f--,c-=8);for(e=s&(1<<-c)-1,s>>=-c,c+=n;0<c;e=256*e+t[f],f--,c-=8);if(0===s)s=1-u;else{if(s===o)return e?NaN:a?-C:C;e+=W(2,n),s-=u}return(a?-1:1)*e*W(2,s-n)}function o(t){return t[3]<<24|t[2]<<16|t[1]<<8|t[0]}function u(t){return[255&t]}function c(t){return[255&t,t>>8&255]}function f(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]}function a(t){return e(t,52,8)}function s(t){return e(t,23,4)}function l(t,n,r){M(t[I],n,{get:function(){return this[r]}})}function h(t,n,r,e){var i=O(+r);if(i+n>t[H])throw R(L);var o=t[K]._b,u=i+t[J],c=o.slice(u,u+n);return e?c:c.reverse()}function v(t,n,r,e,i,o){var u=O(+r);if(u+n>t[H])throw R(L);for(var c=t[K]._b,f=u+t[J],a=e(+i),s=0;s<n;s++)c[f+s]=a[o?s:n-s-1]}var p=r(3),d=r(10),y=r(68),g=r(132),b=r(26),x=r(76),m=r(4),S=r(70),w=r(49),_=r(8),O=r(194),E=r(73).f,M=r(11).f,P=r(137),j=r(83),F="ArrayBuffer",A="DataView",I="prototype",L="Wrong index!",N=p[F],T=p[A],k=p.Math,R=p.RangeError,C=p.Infinity,D=N,G=k.abs,W=k.pow,U=k.floor,V=k.log,B=k.LN2,q="byteLength",z="byteOffset",K=d?"_b":"buffer",H=d?"_l":q,J=d?"_o":z;if(g.ABV){if(!m(function(){N(1)})||!m(function(){new N(-1)})||m(function(){return new N,new N(1.5),new N(NaN),N.name!=F})){for(var $,Y=(N=function(t){return S(this,N),new D(O(t))})[I]=D[I],X=E(D),Q=0;X.length>Q;)($=X[Q++])in N||b(N,$,D[$]);y||(Y.constructor=N)}var Z=new T(new N(2)),tt=T[I].setInt8;Z.setInt8(0,2147483648),Z.setInt8(1,2147483649),!Z.getInt8(0)&&Z.getInt8(1)||x(T[I],{setInt8:function(t,n){tt.call(this,t,n<<24>>24)},setUint8:function(t,n){tt.call(this,t,n<<24>>24)}},!0)}else N=function(t){S(this,N,F);var n=O(t);this._b=P.call(new Array(n),0),this[H]=n},T=function(t,n,r){S(this,T,A),S(t,N,A);var e=t[H],i=w(n);if(i<0||e<i)throw R("Wrong offset!");if(e<i+(r=void 0===r?e-i:_(r)))throw R("Wrong length!");this[K]=t,this[J]=i,this[H]=r},d&&(l(N,q,"_l"),l(T,"buffer","_b"),l(T,q,"_l"),l(T,z,"_o")),x(T[I],{getInt8:function(t){return h(this,1,t)[0]<<24>>24},getUint8:function(t){return h(this,1,t)[0]},getInt16:function(t){var n=h(this,2,t,arguments[1]);return(n[1]<<8|n[0])<<16>>16},getUint16:function(t){var n=h(this,2,t,arguments[1]);return n[1]<<8|n[0]},getInt32:function(t){return o(h(this,4,t,arguments[1]))},getUint32:function(t){return o(h(this,4,t,arguments[1]))>>>0},getFloat32:function(t){return i(h(this,4,t,arguments[1]),23,4)},getFloat64:function(t){return i(h(this,8,t,arguments[1]),52,8)},setInt8:function(t,n){v(this,1,t,u,n)},setUint8:function(t,n){v(this,1,t,u,n)},setInt16:function(t,n){v(this,2,t,c,n,arguments[2])},setUint16:function(t,n){v(this,2,t,c,n,arguments[2])},setInt32:function(t,n){v(this,4,t,f,n,arguments[2])},setUint32:function(t,n){v(this,4,t,f,n,arguments[2])},setFloat32:function(t,n){v(this,4,t,s,n,arguments[2])},setFloat64:function(t,n){v(this,8,t,a,n,arguments[2])}});j(N,F),j(T,A),b(T[I],g.VIEW,!0),n[F]=N,n[A]=T},function(t,n,r){var e=r(3),i=r(46),o=r(68),u=r(195),c=r(11).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){var e=r(81),i=r(7)("iterator"),o=r(82);t.exports=r(46).getIteratorMethod=function(t){if(null!=t)return t[i]||t["@@iterator"]||o[e(t)]}},function(t,n,r){"use strict";var e=r(67),i=r(178),o=r(82),u=r(33);t.exports=r(147)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):i(0,"keys"==n?r:"values"==n?t[r]:[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n){t.exports=function(t,n){t.classList?t.classList.add(n):t.className+=" "+n}},function(t,n){t.exports=function(t,n){if(t.classList)t.classList.remove(n);else{var r=new RegExp("(^|\\b)"+n.split(" ").join("|")+"(\\b|$)","gi");t.className=t.className.replace(r," ")}}},function(t,n,r){var e=r(45);t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},function(t,n,r){"use strict";var a=r(17),s=r(78),l=r(8);t.exports=[].copyWithin||function(t,n){var r=a(this),e=l(r.length),i=s(t,e),o=s(n,e),u=2<arguments.length?arguments[2]:void 0,c=Math.min((void 0===u?e:s(u,e))-o,e-i),f=1;for(o<i&&i<o+c&&(f=-1,o+=c-1,i+=c-1);0<c--;)o in r?r[i]=r[o]:delete r[i],i+=f,o+=f;return r}},function(t,n,r){var e=r(71);t.exports=function(t,n){var r=[];return e(t,!1,r.push,r,n),r}},function(t,n,r){var s=r(21),l=r(17),h=r(116),v=r(8);t.exports=function(t,n,r,e,i){s(n);var o=l(t),u=h(o),c=v(o.length),f=i?c-1:0,a=i?-1:1;if(r<2)for(;;){if(f in u){e=u[f],f+=a;break}if(f+=a,i?f<0:c<=f)throw TypeError("Reduce of empty array with no initial value")}for(;i?0<=f:f<c;f+=a)f in u&&(e=n(e,u[f],f,o));return e}},function(t,n,r){"use strict";var o=r(21),u=r(5),c=r(175),f=[].slice,a={};t.exports=Function.bind||function(n){var r=o(this),e=f.call(arguments,1),i=function(){var t=e.concat(f.call(arguments));return this instanceof i?function(t,n,r){if(!(n in a)){for(var e=[],i=0;i<n;i++)e[i]="a["+i+"]";a[n]=Function("F,a","return new F("+e.join(",")+")")}return a[n](t,r)}(r,t.length,t):c(r,t,n)};return u(r.prototype)&&(i.prototype=r.prototype),i}},function(t,n,r){"use strict";var u=r(11).f,c=r(72),f=r(76),a=r(47),s=r(70),l=r(71),e=r(147),i=r(178),o=r(77),h=r(10),v=r(69).fastKey,p=r(80),d=h?"_s":"size",y=function(t,n){var r,e=v(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,o,r,e){var i=t(function(t,n){s(t,i,o,"_i"),t._t=o,t._i=c(null),t._f=void 0,t._l=void 0,t[d]=0,null!=n&&l(n,r,t[e],t)});return f(i.prototype,{clear:function(){for(var t=p(this,o),n=t._i,r=t._f;r;r=r.n)r.r=!0,r.p&&(r.p=r.p.n=void 0),delete n[r.i];t._f=t._l=void 0,t[d]=0},delete:function(t){var n=p(this,o),r=y(n,t);if(r){var e=r.n,i=r.p;delete n._i[r.i],r.r=!0,i&&(i.n=e),e&&(e.p=i),n._f==r&&(n._f=e),n._l==r&&(n._l=i),n[d]--}return!!r},forEach:function(t){p(this,o);for(var n,r=a(t,1<arguments.length?arguments[1]:void 0,3);n=n?n.n:this._f;)for(r(n.v,n.k,this);n&&n.r;)n=n.p},has:function(t){return!!y(p(this,o),t)}}),h&&u(i.prototype,"size",{get:function(){return p(this,o)[d]}}),i},def:function(t,n,r){var e,i,o=y(t,n);return o?o.v=r:(t._l=o={i:i=v(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=o),e&&(e.n=o),t[d]++,"F"!==i&&(t._i[i]=o)),t},getEntry:y,setStrong:function(t,r,n){e(t,r,function(t,n){this._t=p(t,r),this._k=n,this._l=void 0},function(){for(var t=this,n=t._k,r=t._l;r&&r.r;)r=r.p;return t._t&&(t._l=r=r?r.n:t._t._f)?i(0,"keys"==n?r.k:"values"==n?r.v:[r.k,r.v]):(t._t=void 0,i(1))},n?"entries":"values",!n,!0),o(r)}}},function(t,n,r){var e=r(81),i=r(167);t.exports=function(t){return function(){if(e(this)!=t)throw TypeError(t+"#toJSON isn't generic");return i(this)}}},function(t,n,r){"use strict";var u=r(76),c=r(69).getWeak,i=r(2),f=r(5),a=r(70),s=r(71),e=r(50),l=r(30),h=r(80),o=e(5),v=e(6),p=0,d=function(t){return t._l||(t._l=new y)},y=function(){this.a=[]},g=function(t,n){return o(t.a,function(t){return t[0]===n})};y.prototype={get:function(t){var n=g(this,t);if(n)return n[1]},has:function(t){return!!g(this,t)},set:function(t,n){var r=g(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(n){var t=v(this.a,function(t){return t[0]===n});return~t&&this.a.splice(t,1),!!~t}},t.exports={getConstructor:function(t,r,e,i){var o=t(function(t,n){a(t,o,r,"_i"),t._t=r,t._i=p++,t._l=void 0,null!=n&&s(n,e,t[i],t)});return u(o.prototype,{delete:function(t){if(!f(t))return!1;var n=c(t);return!0===n?d(h(this,r)).delete(t):n&&l(n,this._i)&&delete n[this._i]},has:function(t){if(!f(t))return!1;var n=c(t);return!0===n?d(h(this,r)).has(t):n&&l(n,this._i)}}),o},def:function(t,n,r){var e=c(i(n),!0);return!0===e?d(t).set(n,r):e[t._i]=r,t},ufstore:d}},function(t,n,r){"use strict";var p=r(123),d=r(5),y=r(8),g=r(47),b=r(7)("isConcatSpreadable");t.exports=function t(n,r,e,i,o,u,c,f){for(var a,s,l=o,h=0,v=!!c&&g(c,f,3);h<i;){if(h in e){if(a=v?v(e[h],h,r):e[h],s=!1,d(a)&&(s=void 0!==(s=a[b])?!!s:p(a)),s&&0<u)l=t(n,r,a,y(a.length),l,u-1)-1;else{if(9007199254740991<=l)throw TypeError();n[l]=a}l++}h++}return l}},function(t,n,r){t.exports=!r(10)&&!r(4)(function(){return 7!=Object.defineProperty(r(140)("div"),"a",{get:function(){return 7}}).a})},function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},function(t,n,r){var e=r(5),i=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&i(t)===t}},function(t,n,r){var o=r(2);t.exports=function(t,n,r,e){try{return e?n(o(r)[0],r[1]):n(r)}catch(n){var i=t.return;throw void 0!==i&&o(i.call(t)),n}}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n,r){var o=r(149),e=Math.pow,u=e(2,-52),c=e(2,-23),f=e(2,127)*(2-c),a=e(2,-126);t.exports=Math.fround||function(t){var n,r,e=Math.abs(t),i=o(t);return e<a?i*(e/a/c+1/u-1/u)*a*c:f<(r=(n=(1+c/u)*e)-(n-e))||r!=r?i*(1/0):i*r}},function(t,n){t.exports=Math.log1p||function(t){return-1e-8<(t=+t)&&t<1e-8?t-t*t/2:Math.log(1+t)}},function(t,n){t.exports=Math.scale||function(t,n,r,e,i){return 0===arguments.length||t!=t||n!=n||r!=r||e!=e||i!=i?NaN:t===1/0||t===-1/0?t:(t-n)*(i-e)/(r-n)+e}},function(t,n,r){"use strict";var h=r(10),v=r(74),p=r(127),d=r(117),y=r(17),g=r(116),i=Object.assign;t.exports=!i||r(4)(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=i({},t)[r]||Object.keys(i({},n)).join("")!=e})?function(t,n){for(var r=y(t),e=arguments.length,i=1,o=p.f,u=d.f;i<e;)for(var c,f=g(arguments[i++]),a=o?v(f).concat(o(f)):v(f),s=a.length,l=0;l<s;)c=a[l++],h&&!u.call(f,c)||(r[c]=f[c]);return r}:i},function(t,n,r){var u=r(11),c=r(2),f=r(74);t.exports=r(10)?Object.defineProperties:function(t,n){c(t);for(var r,e=f(n),i=e.length,o=0;o<i;)u.f(t,r=e[o++],n[r]);return t}},function(t,n,r){var e=r(33),i=r(73).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?function(t){try{return i(t)}catch(t){return u.slice()}}(t):i(e(t))}},function(t,n,r){var u=r(30),c=r(33),f=r(120)(!1),a=r(154)("IE_PROTO");t.exports=function(t,n){var r,e=c(t),i=0,o=[];for(r in e)r!=a&&u(e,r)&&o.push(r);for(;n.length>i;)u(e,r=n[i++])&&(~f(o,r)||o.push(r));return o}},function(t,n,r){var f=r(10),a=r(74),s=r(33),l=r(117).f;t.exports=function(c){return function(t){for(var n,r=s(t),e=a(r),i=e.length,o=0,u=[];o<i;)n=e[o++],f&&!l.call(r,n)||u.push(c?[n,r[n]]:r[n]);return u}}},function(t,n,r){var e=r(73),i=r(127),o=r(2),u=r(3).Reflect;t.exports=u&&u.ownKeys||function(t){var n=e.f(o(t)),r=i.f;return r?n.concat(r(t)):n}},function(t,n,r){var e=r(3).parseFloat,i=r(84).trim;t.exports=1/e(r(157)+"-0")!=-1/0?function(t){var n=i(String(t),3),r=e(n);return 0===r&&"-"==n.charAt(0)?-0:r}:e},function(t,n,r){var e=r(3).parseInt,i=r(84).trim,o=r(157),u=/^[-+]?0[xX]/;t.exports=8!==e(o+"08")||22!==e(o+"0x16")?function(t,n){var r=i(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},function(t,n){t.exports=function(t){try{return{e:!1,v:t()}}catch(t){return{e:!0,v:t}}}},function(t,n,r){var e=r(2),i=r(5),o=r(151);t.exports=function(t,n){if(e(t),i(n)&&n.constructor===t)return n;var r=o.f(t);return(0,r.resolve)(n),r.promise}},function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},function(t,n,r){var s=r(8),l=r(156),h=r(51);t.exports=function(t,n,r,e){var i=String(h(t)),o=i.length,u=void 0===r?" ":String(r),c=s(n);if(c<=o||""==u)return i;var f=c-o,a=l.call(u,Math.ceil(f/u.length));return a.length>f&&(a=a.slice(0,f)),e?a+i:i+a}},function(t,n,r){var e=r(49),i=r(8);t.exports=function(t){if(void 0===t)return 0;var n=e(t),r=i(n);if(n!==r)throw RangeError("Wrong length!");return r}},function(t,n,r){n.f=r(7)},function(t,n,r){"use strict";var e=r(170),i=r(80);t.exports=r(121)("Map",function(t){return function(){return t(this,0<arguments.length?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(i(this,"Map"),t);return n&&n.v},set:function(t,n){return e.def(i(this,"Map"),0===t?0:t,n)}},e,!0)},function(t,n,r){"use strict";var e=r(152);r(1)({target:"RegExp",proto:!0,forced:e!==/./.exec},{exec:e})},function(t,n,r){r(10)&&"g"!=/./g.flags&&r(11).f(RegExp.prototype,"flags",{configurable:!0,get:r(115)})},function(t,n,r){"use strict";var e=r(170),i=r(80);t.exports=r(121)("Set",function(t){return function(){return t(this,0<arguments.length?arguments[0]:void 0)}},{add:function(t){return e.def(i(this,"Set"),t=0===t?0:t,t)}},e)},function(t,n,r){"use strict";var o,e=r(3),i=r(50)(0),u=r(27),c=r(69),f=r(182),a=r(172),s=r(5),l=r(80),h=r(80),v=!e.ActiveXObject&&"ActiveXObject"in e,p="WeakMap",d=c.getWeak,y=Object.isExtensible,g=a.ufstore,b=function(t){return function(){return t(this,0<arguments.length?arguments[0]:void 0)}},x={get:function(t){if(s(t)){var n=d(t);return!0===n?g(l(this,p)).get(t):n?n[this._i]:void 0}},set:function(t,n){return a.def(l(this,p),t,n)}},m=t.exports=r(121)(p,b,x,a,!0,!0);h&&v&&(f((o=a.getConstructor(b,p)).prototype,x),c.NEED=!0,i(["delete","has","get","set"],function(e){var t=m.prototype,i=t[e];u(t,e,function(t,n){if(!s(t)||y(t))return i.call(this,t,n);this._f||(this._f=new o);var r=this._f[e](t,n);return"set"==e?this:r})}))},,,,function(t,n){"use strict";t.exports={init:function(){var t=document.querySelector("#page-nav");t&&!document.querySelector("#page-nav .extend.prev")&&(t.innerHTML='<a class="extend prev disabled" rel="prev">&laquo; Prev</a>'+t.innerHTML),t&&!document.querySelector("#page-nav .extend.next")&&(t.innerHTML=t.innerHTML+'<a class="extend next disabled" rel="next">Next &raquo;</a>'),yiliaConfig&&yiliaConfig.open_in_new&&document.querySelectorAll(".article-entry a:not(.article-more-a)").forEach(function(t){var n=t.getAttribute("target");n&&""!==n||t.setAttribute("target","_blank")}),yiliaConfig&&yiliaConfig.toc_hide_index&&document.querySelectorAll(".toc-number").forEach(function(t){t.style.display="none"})}}},function(t,n,r){"use strict";function e(t){return t&&t.__esModule?t:{default:t}}function i(t,n,r,e,i){var o=function(t){for(var n=t.offsetLeft,r=t.offsetParent;null!==r;)n+=r.offsetLeft,r=r.offsetParent;return n}(t),u=function(t){for(var n=t.offsetTop,r=t.offsetParent;null!==r;)n+=r.offsetTop,r=r.offsetParent;return n}(t)-n;if(u-r<=i){var c=t.$newDom;c||(c=t.cloneNode(!0),(0,a.default)(t,c),(t.$newDom=c).style.position="fixed",c.style.top=(r||u)+"px",c.style.left=o+"px",c.style.zIndex=e||2,c.style.width="100%",c.style.color="#fff"),c.style.visibility="visible",t.style.visibility="hidden"}else{t.style.visibility="visible";var f=t.$newDom;f&&(f.style.visibility="hidden")}}function o(){var t=document.querySelector(".js-overlay"),n=document.querySelector(".js-header-menu");i(t,document.body.scrollTop,-63,2,0),i(n,document.body.scrollTop,1,3,0)}var f=e(r(163)),a=e((e(r(164)),r(414))),u=e(r(134)),c=e(r(204)),s=r(135);u.default.versions.mobile&&window.screen.width<800&&(function(){for(var t=document.querySelectorAll(".js-header-menu li a"),n=window.location.pathname,r=0,e=t.length;r<e;r++){var i=t[r];o=n,u=i.getAttribute("href"),c=/\/|index.html/g,o.replace(c,"")===u.replace(c,"")&&(0,f.default)(i,"active")}var o,u,c}(),document.querySelector("#container").addEventListener("scroll",function(t){o()}),window.addEventListener("scroll",function(t){o()}),o()),(0,s.addLoadEvent)(function(){c.default.init()}),t.exports={}},,,function(t,n,r){(function(t){"use strict";function n(t,n,r){t[n]||Object.defineProperty(t,n,{writable:!0,configurable:!0,value:r})}if(r(413),r(209),r(211),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed");t._babelPolyfill=!0;n(String.prototype,"padLeft","".padStart),n(String.prototype,"padRight","".padEnd),"pop,reverse,shift,keys,values,entries,indexOf,every,some,forEach,map,filter,find,findIndex,includes,join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill".split(",").forEach(function(t){[][t]&&n(Array,t,Function.call.bind([][t]))})}).call(n,function(){return this}())},function(L,t){(function(t){!function(t){"use strict";function o(t,n,r,e){var o,u,c,f,i=n&&n.prototype instanceof h?n:h,a=Object.create(i.prototype),s=new p(e||[]);return a._invoke=(o=t,u=r,c=s,f=_,function(t,n){if(f===E)throw new Error("Generator is already running");if(f===M){if("throw"===t)throw n;return d()}for(c.method=t,c.arg=n;;){var r=c.delegate;if(r){var e=v(r,c);if(e){if(e===P)continue;return e}}if("next"===c.method)c.sent=c._sent=c.arg;else if("throw"===c.method){if(f===_)throw f=M,c.arg;c.dispatchException(c.arg)}else"return"===c.method&&c.abrupt("return",c.arg);f=E;var i=l(o,u,c);if("normal"===i.type){if(f=c.done?M:O,i.arg===P)continue;return{value:i.arg,done:c.done}}"throw"===i.type&&(f=M,c.method="throw",c.arg=i.arg)}}),a}function l(t,n,r){try{return{type:"normal",arg:t.call(n,r)}}catch(t){return{type:"throw",arg:t}}}function h(){}function r(){}function n(){}function e(t){["next","throw","return"].forEach(function(n){t[n]=function(t){return this._invoke(n,t)}})}function u(c){function f(t,n,r,e){var i=l(c[t],c,n);if("throw"!==i.type){var o=i.arg,u=o.value;return u&&"object"==typeof u&&y.call(u,"__await")?Promise.resolve(u.__await).then(function(t){f("next",t,r,e)},function(t){f("throw",t,r,e)}):Promise.resolve(u).then(function(t){o.value=t,r(o)},e)}e(i.arg)}var n;"object"==typeof t.process&&t.process.domain&&(f=t.process.domain.bind(f)),this._invoke=function(r,e){function t(){return new Promise(function(t,n){f(r,e,t,n)})}return n=n?n.then(t,t):t()}}function v(t,n){var r=t.iterator[n.method];if(r===a){if(n.delegate=null,"throw"===n.method){if(t.iterator.return&&(n.method="return",n.arg=a,v(t,n),"throw"===n.method))return P;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return P}var e=l(r,t.iterator,n.arg);if("throw"===e.type)return n.method="throw",n.arg=e.arg,n.delegate=null,P;var i=e.arg;return i?i.done?(n[t.resultName]=i.value,n.next=t.nextLoc,"return"!==n.method&&(n.method="next",n.arg=a),n.delegate=null,P):i:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,P)}function i(t){var n={tryLoc:t[0]};1 in t&&(n.catchLoc=t[1]),2 in t&&(n.finallyLoc=t[2],n.afterLoc=t[3]),this.tryEntries.push(n)}function c(t){var n=t.completion||{};n.type="normal",delete n.arg,t.completion=n}function p(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(i,this),this.reset(!0)}function f(n){if(n){var t=n[b];if(t)return t.call(n);if("function"==typeof n.next)return n;if(!isNaN(n.length)){var r=-1,e=function t(){for(;++r<n.length;)if(y.call(n,r))return t.value=n[r],t.done=!1,t;return t.value=a,t.done=!0,t};return e.next=e}}return{next:d}}function d(){return{value:a,done:!0}}var a,s=Object.prototype,y=s.hasOwnProperty,g="function"==typeof Symbol?Symbol:{},b=g.iterator||"@@iterator",x=g.asyncIterator||"@@asyncIterator",m=g.toStringTag||"@@toStringTag",S="object"==typeof L,w=t.regeneratorRuntime;if(w)S&&(L.exports=w);else{(w=t.regeneratorRuntime=S?L.exports:{}).wrap=o;var _="suspendedStart",O="suspendedYield",E="executing",M="completed",P={},j={};j[b]=function(){return this};var F=Object.getPrototypeOf,A=F&&F(F(f([])));A&&A!==s&&y.call(A,b)&&(j=A);var I=n.prototype=h.prototype=Object.create(j);r.prototype=I.constructor=n,n.constructor=r,n[m]=r.displayName="GeneratorFunction",w.isGeneratorFunction=function(t){var n="function"==typeof t&&t.constructor;return!!n&&(n===r||"GeneratorFunction"===(n.displayName||n.name))},w.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,n):(t.__proto__=n,m in t||(t[m]="GeneratorFunction")),t.prototype=Object.create(I),t},w.awrap=function(t){return{__await:t}},e(u.prototype),u.prototype[x]=function(){return this},w.AsyncIterator=u,w.async=function(t,n,r,e){var i=new u(o(t,n,r,e));return w.isGeneratorFunction(n)?i:i.next().then(function(t){return t.done?t.value:i.next()})},e(I),I[m]="Generator",I[b]=function(){return this},I.toString=function(){return"[object Generator]"},w.keys=function(r){var e=[];for(var t in r)e.push(t);return e.reverse(),function t(){for(;e.length;){var n=e.pop();if(n in r)return t.value=n,t.done=!1,t}return t.done=!0,t}},w.values=f,p.prototype={constructor:p,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=a,this.done=!1,this.delegate=null,this.method="next",this.arg=a,this.tryEntries.forEach(c),!t)for(var n in this)"t"===n.charAt(0)&&y.call(this,n)&&!isNaN(+n.slice(1))&&(this[n]=a)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(r){function t(t,n){return o.type="throw",o.arg=r,e.next=t,n&&(e.method="next",e.arg=a),!!n}if(this.done)throw r;for(var e=this,n=this.tryEntries.length-1;0<=n;--n){var i=this.tryEntries[n],o=i.completion;if("root"===i.tryLoc)return t("end");if(i.tryLoc<=this.prev){var u=y.call(i,"catchLoc"),c=y.call(i,"finallyLoc");if(u&&c){if(this.prev<i.catchLoc)return t(i.catchLoc,!0);if(this.prev<i.finallyLoc)return t(i.finallyLoc)}else if(u){if(this.prev<i.catchLoc)return t(i.catchLoc,!0)}else{if(!c)throw new Error("try statement without catch or finally");if(this.prev<i.finallyLoc)return t(i.finallyLoc)}}}},abrupt:function(t,n){for(var r=this.tryEntries.length-1;0<=r;--r){var e=this.tryEntries[r];if(e.tryLoc<=this.prev&&y.call(e,"finallyLoc")&&this.prev<e.finallyLoc){var i=e;break}}i&&("break"===t||"continue"===t)&&i.tryLoc<=n&&n<=i.finallyLoc&&(i=null);var o=i?i.completion:{};return o.type=t,o.arg=n,i?(this.method="next",this.next=i.finallyLoc,P):this.complete(o)},complete:function(t,n){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&n&&(this.next=n),P},finish:function(t){for(var n=this.tryEntries.length-1;0<=n;--n){var r=this.tryEntries[n];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),c(r),P}},catch:function(t){for(var n=this.tryEntries.length-1;0<=n;--n){var r=this.tryEntries[n];if(r.tryLoc===t){var e=r.completion;if("throw"===e.type){var i=e.arg;c(r)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(t,n,r){return this.delegate={iterator:f(t),resultName:n,nextLoc:r},"next"===this.method&&(this.arg=a),P}}}}("object"==typeof t?t:"object"==typeof window?window:"object"==typeof self?self:this)}).call(t,function(){return this}())},,function(t,n,r){r(221),t.exports=r(46).RegExp.escape},,,,function(t,n,r){var e=r(5),i=r(123),o=r(7)("species");t.exports=function(t){var n;return i(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!i(n.prototype)||(n=void 0),e(n)&&(null===(n=n[o])&&(n=void 0))),void 0===n?Array:n}},function(t,n,r){"use strict";var e=r(4),i=Date.prototype.getTime,o=Date.prototype.toISOString,u=function(t){return 9<t?t:"0"+t};t.exports=e(function(){return"0385-07-25T07:06:39.999Z"!=o.call(new Date(-5e13-1))})||!e(function(){o.call(new Date(NaN))})?function(){if(!isFinite(i.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":9999<n?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(99<r?r:"0"+u(r))+"Z"}:o},function(t,n,r){"use strict";var e=r(2),i=r(53);t.exports=function(t){if("string"!==t&&"number"!==t&&"default"!==t)throw TypeError("Incorrect hint");return i(e(this),"number"!=t)}},function(t,n,r){var c=r(74),f=r(127),a=r(117);t.exports=function(t){var n=c(t),r=f.f;if(r)for(var e,i=r(t),o=a.f,u=0;i.length>u;)o.call(t,e=i[u++])&&n.push(e);return n}},function(t,n,r){t.exports=r(118)("native-function-to-string",Function.toString)},function(t,n){t.exports=function(n,r){var e=r===Object(r)?function(t){return r[t]}:r;return function(t){return String(t).replace(n,e)}}},function(t,n,r){var e=r(1),i=r(220)(/[\\^$*+?.()|[\]{}]/g,"\\$&");e(e.S,"RegExp",{escape:function(t){return i(t)}})},function(t,n,r){var e=r(1);e(e.P,"Array",{copyWithin:r(166)}),r(67)("copyWithin")},function(t,n,r){"use strict";var e=r(1),i=r(50)(4);e(e.P+e.F*!r(48)([].every,!0),"Array",{every:function(t){return i(this,t,arguments[1])}})},function(t,n,r){var e=r(1);e(e.P,"Array",{fill:r(137)}),r(67)("fill")},function(t,n,r){"use strict";var e=r(1),i=r(50)(2);e(e.P+e.F*!r(48)([].filter,!0),"Array",{filter:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(50)(6),o="findIndex",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{findIndex:function(t){return i(this,t,1<arguments.length?arguments[1]:void 0)}}),r(67)(o)},function(t,n,r){"use strict";var e=r(1),i=r(50)(5),o="find",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{find:function(t){return i(this,t,1<arguments.length?arguments[1]:void 0)}}),r(67)(o)},function(t,n,r){"use strict";var e=r(1),i=r(50)(0),o=r(48)([].forEach,!0);e(e.P+e.F*!o,"Array",{forEach:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var h=r(47),e=r(1),v=r(17),p=r(177),d=r(145),y=r(8),g=r(139),b=r(161);e(e.S+e.F*!r(125)(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,e,i,o=v(t),u="function"==typeof this?this:Array,c=arguments.length,f=1<c?arguments[1]:void 0,a=void 0!==f,s=0,l=b(o);if(a&&(f=h(f,2<c?arguments[2]:void 0,2)),null==l||u==Array&&d(l))for(r=new u(n=y(o.length));s<n;s++)g(r,s,a?f(o[s],s):o[s]);else for(i=l.call(o),r=new u;!(e=i.next()).done;s++)g(r,s,a?p(i,f,[e.value,s],!0):e.value);return r.length=s,r}})},function(t,n,r){"use strict";var e=r(1),i=r(120)(!1),o=[].indexOf,u=!!o&&1/[1].indexOf(1,-0)<0;e(e.P+e.F*(u||!r(48)(o)),"Array",{indexOf:function(t){return u?o.apply(this,arguments)||0:i(this,t,arguments[1])}})},function(t,n,r){var e=r(1);e(e.S,"Array",{isArray:r(123)})},function(t,n,r){"use strict";var e=r(1),i=r(33),o=[].join;e(e.P+e.F*(r(116)!=Object||!r(48)(o)),"Array",{join:function(t){return o.call(i(this),void 0===t?",":t)}})},function(t,n,r){"use strict";var e=r(1),i=r(33),o=r(49),u=r(8),c=[].lastIndexOf,f=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(f||!r(48)(c)),"Array",{lastIndexOf:function(t){if(f)return c.apply(this,arguments)||0;var n=i(this),r=u(n.length),e=r-1;for(1<arguments.length&&(e=Math.min(e,o(arguments[1]))),e<0&&(e=r+e);0<=e;e--)if(e in n&&n[e]===t)return e||0;return-1}})},function(t,n,r){"use strict";var e=r(1),i=r(50)(1);e(e.P+e.F*!r(48)([].map,!0),"Array",{map:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(139);e(e.S+e.F*r(4)(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);t<n;)i(r,t,arguments[t++]);return r.length=n,r}})},function(t,n,r){"use strict";var e=r(1),i=r(168);e(e.P+e.F*!r(48)([].reduceRight,!0),"Array",{reduceRight:function(t){return i(this,t,arguments.length,arguments[1],!0)}})},function(t,n,r){"use strict";var e=r(1),i=r(168);e(e.P+e.F*!r(48)([].reduce,!0),"Array",{reduce:function(t){return i(this,t,arguments.length,arguments[1],!1)}})},function(t,n,r){"use strict";var e=r(1),i=r(143),a=r(45),s=r(78),l=r(8),h=[].slice;e(e.P+e.F*r(4)(function(){i&&h.call(i)}),"Array",{slice:function(t,n){var r=l(this.length),e=a(this);if(n=void 0===n?r:n,"Array"==e)return h.call(this,t,n);for(var i=s(t,r),o=s(n,r),u=l(o-i),c=new Array(u),f=0;f<u;f++)c[f]="String"==e?this.charAt(i+f):this[i+f];return c}})},function(t,n,r){"use strict";var e=r(1),i=r(50)(3);e(e.P+e.F*!r(48)([].some,!0),"Array",{some:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(21),o=r(17),u=r(4),c=[].sort,f=[1,2,3];e(e.P+e.F*(u(function(){f.sort(void 0)})||!u(function(){f.sort(null)})||!r(48)(c)),"Array",{sort:function(t){return void 0===t?c.call(o(this)):c.call(o(this),i(t))}})},function(t,n,r){r(77)("Array")},function(t,n,r){var e=r(1);e(e.S,"Date",{now:function(){return(new Date).getTime()}})},function(t,n,r){var e=r(1),i=r(216);e(e.P+e.F*(Date.prototype.toISOString!==i),"Date",{toISOString:i})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(53);e(e.P+e.F*r(4)(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=i(this),r=o(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},function(t,n,r){var e=r(7)("toPrimitive"),i=Date.prototype;e in i||r(26)(i,e,r(217))},function(t,n,r){var e=Date.prototype,i="Invalid Date",o="toString",u=e[o],c=e.getTime;new Date(NaN)+""!=i&&r(27)(e,o,function(){var t=c.call(this);return t==t?u.call(this):i})},function(t,n,r){var e=r(1);e(e.P,"Function",{bind:r(169)})},function(t,n,r){"use strict";var e=r(5),i=r(32),o=r(7)("hasInstance"),u=Function.prototype;o in u||r(11).f(u,o,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=i(t);)if(this.prototype===t)return!0;return!1}})},function(t,n,r){var e=r(11).f,i=Function.prototype,o=/^\s*function ([^ (]*)/;"name"in i||r(10)&&e(i,"name",{configurable:!0,get:function(){try{return(""+this).match(o)[1]}catch(t){return""}}})},function(t,n,r){var e=r(1),i=r(180),o=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:94906265.62425156<t?Math.log(t)+Math.LN2:i(t-1+o(t-1)*o(t+1))}})},function(t,n,r){var e=r(1),i=Math.asinh;e(e.S+e.F*!(i&&0<1/i(0)),"Math",{asinh:function t(n){return isFinite(n=+n)&&0!=n?n<0?-t(-n):Math.log(n+Math.sqrt(n*n+1)):n}})},function(t,n,r){var e=r(1),i=Math.atanh;e(e.S+e.F*!(i&&1/i(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},function(t,n,r){var e=r(1),i=r(149);e(e.S,"Math",{cbrt:function(t){return i(t=+t)*Math.pow(Math.abs(t),1/3)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},function(t,n,r){var e=r(1),i=Math.exp;e(e.S,"Math",{cosh:function(t){return(i(t=+t)+i(-t))/2}})},function(t,n,r){var e=r(1),i=r(148);e(e.S+e.F*(i!=Math.expm1),"Math",{expm1:i})},function(t,n,r){var e=r(1);e(e.S,"Math",{fround:r(179)})},function(t,n,r){var e=r(1),f=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,i=0,o=0,u=arguments.length,c=0;o<u;)c<(r=f(arguments[o++]))?(i=i*(e=c/r)*e+1,c=r):0<r?i+=(e=r/c)*e:i+=r;return c===1/0?1/0:c*Math.sqrt(i)}})},function(t,n,r){var e=r(1),i=Math.imul;e(e.S+e.F*r(4)(function(){return-5!=i(4294967295,5)||2!=i.length}),"Math",{imul:function(t,n){var r=65535,e=+t,i=+n,o=r&e,u=r&i;return 0|o*u+((r&e>>>16)*u+o*(r&i>>>16)<<16>>>0)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{log10:function(t){return Math.log(t)*Math.LOG10E}})},function(t,n,r){var e=r(1);e(e.S,"Math",{log1p:r(180)})},function(t,n,r){var e=r(1);e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},function(t,n,r){var e=r(1);e(e.S,"Math",{sign:r(149)})},function(t,n,r){var e=r(1),i=r(148),o=Math.exp;e(e.S+e.F*r(4)(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(i(t)-i(-t))/2:(o(t-1)-o(-t-1))*(Math.E/2)}})},function(t,n,r){var e=r(1),i=r(148),o=Math.exp;e(e.S,"Math",{tanh:function(t){var n=i(t=+t),r=i(-t);return n==1/0?1:r==1/0?-1:(n-r)/(o(t)+o(-t))}})},function(t,n,r){var e=r(1);e(e.S,"Math",{trunc:function(t){return(0<t?Math.floor:Math.ceil)(t)}})},function(t,n,r){"use strict";var e=r(3),i=r(30),o=r(45),u=r(144),s=r(53),c=r(4),f=r(73).f,a=r(31).f,l=r(11).f,h=r(84).trim,v="Number",p=e[v],d=p,y=p.prototype,g=o(r(72)(y))==v,b="trim"in String.prototype,x=function(t){var n=s(t,!1);if("string"==typeof n&&2<n.length){var r,e,i,o=(n=b?n.trim():h(n,3)).charCodeAt(0);if(43===o||45===o){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===o){switch(n.charCodeAt(1)){case 66:case 98:e=2,i=49;break;case 79:case 111:e=8,i=55;break;default:return+n}for(var u,c=n.slice(2),f=0,a=c.length;f<a;f++)if((u=c.charCodeAt(f))<48||i<u)return NaN;return parseInt(c,e)}}return+n};if(!p(" 0o1")||!p("0b1")||p("+0x1")){p=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof p&&(g?c(function(){y.valueOf.call(r)}):o(r)!=v)?u(new d(x(n)),r,p):x(n)};for(var m,S=r(10)?f(d):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),w=0;S.length>w;w++)i(d,m=S[w])&&!i(p,m)&&l(p,m,a(d,m));(p.prototype=y).constructor=p,r(27)(e,v,p)}},function(t,n,r){var e=r(1);e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},function(t,n,r){var e=r(1),i=r(3).isFinite;e(e.S,"Number",{isFinite:function(t){return"number"==typeof t&&i(t)}})},function(t,n,r){var e=r(1);e(e.S,"Number",{isInteger:r(176)})},function(t,n,r){var e=r(1);e(e.S,"Number",{isNaN:function(t){return t!=t}})},function(t,n,r){var e=r(1),i=r(176),o=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return i(t)&&o(t)<=9007199254740991}})},function(t,n,r){var e=r(1);e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},function(t,n,r){var e=r(1);e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},function(t,n,r){var e=r(1),i=r(188);e(e.S+e.F*(Number.parseFloat!=i),"Number",{parseFloat:i})},function(t,n,r){var e=r(1),i=r(189);e(e.S+e.F*(Number.parseInt!=i),"Number",{parseInt:i})},function(t,n,r){"use strict";var e=r(1),a=r(49),s=r(165),l=r(156),i=1..toFixed,o=Math.floor,u=[0,0,0,0,0,0],h="Number.toFixed: incorrect invocation!",v=function(t,n){for(var r=-1,e=n;++r<6;)e+=t*u[r],u[r]=e%1e7,e=o(e/1e7)},p=function(t){for(var n=6,r=0;0<=--n;)r+=u[n],u[n]=o(r/t),r=r%t*1e7},d=function(){for(var t=6,n="";0<=--t;)if(""!==n||0===t||0!==u[t]){var r=String(u[t]);n=""===n?r:n+l.call("0",7-r.length)+r}return n},y=function(t,n,r){return 0===n?r:n%2==1?y(t,n-1,r*t):y(t*t,n/2,r)};e(e.P+e.F*(!!i&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r(4)(function(){i.call({})})),"Number",{toFixed:function(t){var n,r,e,i,o=s(this,h),u=a(t),c="",f="0";if(u<0||20<u)throw RangeError(h);if(o!=o)return"NaN";if(o<=-1e21||1e21<=o)return String(o);if(o<0&&(c="-",o=-o),1e-21<o)if(r=(n=function(t){for(var n=0,r=t;4096<=r;)n+=12,r/=4096;for(;2<=r;)n+=1,r/=2;return n}(o*y(2,69,1))-69)<0?o*y(2,-n,1):o/y(2,n,1),r*=4503599627370496,0<(n=52-n)){for(v(0,r),e=u;7<=e;)v(1e7,0),e-=7;for(v(y(10,e,1),0),e=n-1;23<=e;)p(1<<23),e-=23;p(1<<e),v(1,1),p(2),f=d()}else v(0,r),v(1<<-n,0),f=d()+l.call("0",u);return f=0<u?c+((i=f.length)<=u?"0."+l.call("0",u-i)+f:f.slice(0,i-u)+"."+f.slice(i-u)):c+f}})},function(t,n,r){"use strict";var e=r(1),i=r(4),o=r(165),u=1..toPrecision;e(e.P+e.F*(i(function(){return"1"!==u.call(1,void 0)})||!i(function(){u.call({})})),"Number",{toPrecision:function(t){var n=o(this,"Number#toPrecision: incorrect invocation!");return void 0===t?u.call(n):u.call(n,t)}})},function(t,n,r){var e=r(1);e(e.S+e.F,"Object",{assign:r(182)})},function(t,n,r){var e=r(1);e(e.S,"Object",{create:r(72)})},function(t,n,r){var e=r(1);e(e.S+e.F*!r(10),"Object",{defineProperties:r(183)})},function(t,n,r){var e=r(1);e(e.S+e.F*!r(10),"Object",{defineProperty:r(11).f})},function(t,n,r){var e=r(5),i=r(69).onFreeze;r(52)("freeze",function(n){return function(t){return n&&e(t)?n(i(t)):t}})},function(t,n,r){var e=r(33),i=r(31).f;r(52)("getOwnPropertyDescriptor",function(){return function(t,n){return i(e(t),n)}})},function(t,n,r){r(52)("getOwnPropertyNames",function(){return r(184).f})},function(t,n,r){var e=r(17),i=r(32);r(52)("getPrototypeOf",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(5);r(52)("isExtensible",function(n){return function(t){return!!e(t)&&(!n||n(t))}})},function(t,n,r){var e=r(5);r(52)("isFrozen",function(n){return function(t){return!e(t)||!!n&&n(t)}})},function(t,n,r){var e=r(5);r(52)("isSealed",function(n){return function(t){return!e(t)||!!n&&n(t)}})},function(t,n,r){var e=r(1);e(e.S,"Object",{is:r(192)})},function(t,n,r){var e=r(17),i=r(74);r(52)("keys",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(5),i=r(69).onFreeze;r(52)("preventExtensions",function(n){return function(t){return n&&e(t)?n(i(t)):t}})},function(t,n,r){var e=r(5),i=r(69).onFreeze;r(52)("seal",function(n){return function(t){return n&&e(t)?n(i(t)):t}})},function(t,n,r){var e=r(1);e(e.S,"Object",{setPrototypeOf:r(153).set})},function(t,n,r){"use strict";var e=r(81),i={};i[r(7)("toStringTag")]="z",i+""!="[object z]"&&r(27)(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},function(t,n,r){var e=r(1),i=r(188);e(e.G+e.F*(parseFloat!=i),{parseFloat:i})},function(t,n,r){var e=r(1),i=r(189);e(e.G+e.F*(parseInt!=i),{parseInt:i})},function(t,n,r){"use strict";var e,i,o,u,c=r(68),f=r(3),a=r(47),s=r(81),l=r(1),h=r(5),v=r(21),p=r(70),d=r(71),y=r(119),g=r(158).set,b=r(150)(),x=r(151),m=r(190),S=r(133),w=r(191),_="Promise",O=f.TypeError,E=f.process,M=E&&E.versions,P=M&&M.v8||"",j=f[_],F="process"==s(E),A=function(){},I=i=x.f,L=!!function(){try{var t=j.resolve(1),n=(t.constructor={})[r(7)("species")]=function(t){t(A,A)};return(F||"function"==typeof PromiseRejectionEvent)&&t.then(A)instanceof n&&0!==P.indexOf("6.6")&&-1===S.indexOf("Chrome/66")}catch(t){}}(),N=function(t){var n;return!(!h(t)||"function"!=typeof(n=t.then))&&n},T=function(s,r){if(!s._n){s._n=!0;var e=s._c;b(function(){for(var f=s._v,a=1==s._s,t=0,n=function(t){var n,r,e,i=a?t.ok:t.fail,o=t.resolve,u=t.reject,c=t.domain;try{i?(a||(2==s._h&&C(s),s._h=1),!0===i?n=f:(c&&c.enter(),n=i(f),c&&(c.exit(),e=!0)),n===t.promise?u(O("Promise-chain cycle")):(r=N(n))?r.call(n,o,u):o(n)):u(f)}catch(t){c&&!e&&c.exit(),u(t)}};e.length>t;)n(e[t++]);s._c=[],s._n=!1,r&&!s._h&&k(s)})}},k=function(o){g.call(f,function(){var t,n,r,e=o._v,i=R(o);if(i&&(t=m(function(){F?E.emit("unhandledRejection",e,o):(n=f.onunhandledrejection)?n({promise:o,reason:e}):(r=f.console)&&r.error&&r.error("Unhandled promise rejection",e)}),o._h=F||R(o)?2:1),o._a=void 0,i&&t.e)throw t.v})},R=function(t){return 1!==t._h&&0===(t._a||t._c).length},C=function(n){g.call(f,function(){var t;F?E.emit("rejectionHandled",n):(t=f.onrejectionhandled)&&t({promise:n,reason:n._v})})},D=function(t){var n=this;n._d||(n._d=!0,(n=n._w||n)._v=t,n._s=2,n._a||(n._a=n._c.slice()),T(n,!0))},G=function(t){var r,e=this;if(!e._d){e._d=!0,e=e._w||e;try{if(e===t)throw O("Promise can't be resolved itself");(r=N(t))?b(function(){var n={_w:e,_d:!1};try{r.call(t,a(G,n,1),a(D,n,1))}catch(t){D.call(n,t)}}):(e._v=t,e._s=1,T(e,!1))}catch(t){D.call({_w:e,_d:!1},t)}}};L||(j=function(t){p(this,j,_,"_h"),v(t),e.call(this);try{t(a(G,this,1),a(D,this,1))}catch(t){D.call(this,t)}},(e=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1}).prototype=r(76)(j.prototype,{then:function(t,n){var r=I(y(this,j));return r.ok="function"!=typeof t||t,r.fail="function"==typeof n&&n,r.domain=F?E.domain:void 0,this._c.push(r),this._a&&this._a.push(r),this._s&&T(this,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),o=function(){var t=new e;this.promise=t,this.resolve=a(G,t,1),this.reject=a(D,t,1)},x.f=I=function(t){return t===j||t===u?new o(t):i(t)}),l(l.G+l.W+l.F*!L,{Promise:j}),r(83)(j,_),r(77)(_),u=r(46)[_],l(l.S+l.F*!L,_,{reject:function(t){var n=I(this);return(0,n.reject)(t),n.promise}}),l(l.S+l.F*(c||!L),_,{resolve:function(t){return w(c&&this===u?j:this,t)}}),l(l.S+l.F*!(L&&r(125)(function(t){j.all(t).catch(A)})),_,{all:function(t){var u=this,n=I(u),c=n.resolve,f=n.reject,r=m(function(){var e=[],i=0,o=1;d(t,!1,function(t){var n=i++,r=!1;e.push(void 0),o++,u.resolve(t).then(function(t){r||(r=!0,e[n]=t,--o||c(e))},f)}),--o||c(e)});return r.e&&f(r.v),n.promise},race:function(t){var n=this,r=I(n),e=r.reject,i=m(function(){d(t,!1,function(t){n.resolve(t).then(r.resolve,e)})});return i.e&&e(i.v),r.promise}})},function(t,n,r){var e=r(1),o=r(21),u=r(2),c=(r(3).Reflect||{}).apply,f=Function.apply;e(e.S+e.F*!r(4)(function(){c(function(){})}),"Reflect",{apply:function(t,n,r){var e=o(t),i=u(r);return c?c(e,n,i):f.call(e,n,i)}})},function(t,n,r){var e=r(1),c=r(72),f=r(21),a=r(2),s=r(5),i=r(4),l=r(169),h=(r(3).Reflect||{}).construct,v=i(function(){function t(){}return!(h(function(){},[],t)instanceof t)}),p=!i(function(){h(function(){})});e(e.S+e.F*(v||p),"Reflect",{construct:function(t,n){f(t),a(n);var r=arguments.length<3?t:f(arguments[2]);if(p&&!v)return h(t,n,r);if(t==r){switch(n.length){case 0:return new t;case 1:return new t(n[0]);case 2:return new t(n[0],n[1]);case 3:return new t(n[0],n[1],n[2]);case 4:return new t(n[0],n[1],n[2],n[3])}var e=[null];return e.push.apply(e,n),new(l.apply(t,e))}var i=r.prototype,o=c(s(i)?i:Object.prototype),u=Function.apply.call(t,o,n);return s(u)?u:o}})},function(t,n,r){var e=r(11),i=r(1),o=r(2),u=r(53);i(i.S+i.F*r(4)(function(){Reflect.defineProperty(e.f({},1,{value:1}),1,{value:2})}),"Reflect",{defineProperty:function(t,n,r){o(t),n=u(n,!0),o(r);try{return e.f(t,n,r),!0}catch(t){return!1}}})},function(t,n,r){var e=r(1),i=r(31).f,o=r(2);e(e.S,"Reflect",{deleteProperty:function(t,n){var r=i(o(t),n);return!(r&&!r.configurable)&&delete t[n]}})},function(t,n,r){"use strict";var e=r(1),i=r(2),o=function(t){this._t=i(t),this._i=0;var n,r=this._k=[];for(n in t)r.push(n)};r(146)(o,"Object",function(){var t,n=this._k;do{if(this._i>=n.length)return{value:void 0,done:!0}}while(!((t=n[this._i++])in this._t));return{value:t,done:!1}}),e(e.S,"Reflect",{enumerate:function(t){return new o(t)}})},function(t,n,r){var e=r(31),i=r(1),o=r(2);i(i.S,"Reflect",{getOwnPropertyDescriptor:function(t,n){return e.f(o(t),n)}})},function(t,n,r){var e=r(1),i=r(32),o=r(2);e(e.S,"Reflect",{getPrototypeOf:function(t){return i(o(t))}})},function(t,n,r){var u=r(31),c=r(32),f=r(30),e=r(1),a=r(5),s=r(2);e(e.S,"Reflect",{get:function t(n,r){var e,i,o=arguments.length<3?n:arguments[2];return s(n)===o?n[r]:(e=u.f(n,r))?f(e,"value")?e.value:void 0!==e.get?e.get.call(o):void 0:a(i=c(n))?t(i,r,o):void 0}})},function(t,n,r){var e=r(1);e(e.S,"Reflect",{has:function(t,n){return n in t}})},function(t,n,r){var e=r(1),i=r(2),o=Object.isExtensible;e(e.S,"Reflect",{isExtensible:function(t){return i(t),!o||o(t)}})},function(t,n,r){var e=r(1);e(e.S,"Reflect",{ownKeys:r(187)})},function(t,n,r){var e=r(1),i=r(2),o=Object.preventExtensions;e(e.S,"Reflect",{preventExtensions:function(t){i(t);try{return o&&o(t),!0}catch(t){return!1}}})},function(t,n,r){var e=r(1),i=r(153);i&&e(e.S,"Reflect",{setPrototypeOf:function(t,n){i.check(t,n);try{return i.set(t,n),!0}catch(t){return!1}}})},function(t,n,r){var f=r(11),a=r(31),s=r(32),l=r(30),e=r(1),h=r(75),v=r(2),p=r(5);e(e.S,"Reflect",{set:function t(n,r,e){var i,o,u=arguments.length<4?n:arguments[3],c=a.f(v(n),r);if(!c){if(p(o=s(n)))return t(o,r,e,u);c=h(0)}if(l(c,"value")){if(!1===c.writable||!p(u))return!1;if(i=a.f(u,r)){if(i.get||i.set||!1===i.writable)return!1;i.value=e,f.f(u,r,i)}else f.f(u,r,h(0,e));return!0}return void 0!==c.set&&(c.set.call(u,e),!0)}})},function(t,n,r){var e=r(3),o=r(144),i=r(11).f,u=r(73).f,c=r(124),f=r(115),a=e.RegExp,s=a,l=a.prototype,h=/a/g,v=/a/g,p=new a(h)!==h;if(r(10)&&(!p||r(4)(function(){return v[r(7)("match")]=!1,a(h)!=h||a(v)==v||"/a/i"!=a(h,"i")}))){a=function(t,n){var r=this instanceof a,e=c(t),i=void 0===n;return!r&&e&&t.constructor===a&&i?t:o(p?new s(e&&!i?t.source:t,n):s((e=t instanceof a)?t.source:t,e&&i?f.call(t):n),r?this:l,a)};for(var d=function(n){n in a||i(a,n,{configurable:!0,get:function(){return s[n]},set:function(t){s[n]=t}})},y=u(s),g=0;y.length>g;)d(y[g++]);(l.constructor=a).prototype=l,r(27)(e,"RegExp",a)}r(77)("RegExp")},function(t,n,r){"use strict";var l=r(2),h=r(8),v=r(136),p=r(128);r(122)("match",1,function(e,i,a,s){return[function(t){var n=e(this),r=null==t?void 0:t[i];return void 0!==r?r.call(t,n):new RegExp(t)[i](String(n))},function(t){var n=s(a,t,this);if(n.done)return n.value;var r=l(t),e=String(this);if(!r.global)return p(r,e);for(var i,o=r.unicode,u=[],c=r.lastIndex=0;null!==(i=p(r,e));){var f=String(i[0]);""===(u[c]=f)&&(r.lastIndex=v(e,h(r.lastIndex),o)),c++}return 0===c?null:u}]})},function(t,n,r){"use strict";var O=r(2),e=r(17),E=r(8),M=r(49),P=r(136),j=r(128),F=Math.max,A=Math.min,h=Math.floor,v=/\$([$&`']|\d\d?|<[^>]*>)/g,p=/\$([$&`']|\d\d?)/g;r(122)("replace",2,function(i,o,S,w){function _(o,u,c,f,a,t){var s=c+o.length,l=f.length,n=p;return void 0!==a&&(a=e(a),n=v),S.call(t,n,function(t,n){var r;switch(n.charAt(0)){case"$":return"$";case"&":return o;case"`":return u.slice(0,c);case"'":return u.slice(s);case"<":r=a[n.slice(1,-1)];break;default:var e=+n;if(0===e)return t;if(l<e){var i=h(e/10);return 0===i?t:i<=l?void 0===f[i-1]?n.charAt(1):f[i-1]+n.charAt(1):t}r=f[e-1]}return void 0===r?"":r})}return[function(t,n){var r=i(this),e=null==t?void 0:t[o];return void 0!==e?e.call(t,r,n):S.call(String(r),t,n)},function(t,n){var r=w(S,t,this,n);if(r.done)return r.value;var e=O(t),i=String(this),o="function"==typeof n;o||(n=String(n));var u,c=e.global;if(c){var f=e.unicode;e.lastIndex=0}for(var a=[];;){var s=j(e,i);if(null===s)break;if(a.push(s),!c)break;""===String(s[0])&&(e.lastIndex=P(i,E(e.lastIndex),f))}for(var l="",h=0,v=0;v<a.length;v++){s=a[v];for(var p=String(s[0]),d=F(A(M(s.index),i.length),0),y=[],g=1;g<s.length;g++)y.push(void 0===(u=s[g])?u:String(u));var b=s.groups;if(o){var x=[p].concat(y,d,i);void 0!==b&&x.push(b);var m=String(n.apply(void 0,x))}else m=_(p,i,d,y,b,n);h<=d&&(l+=i.slice(h,d)+m,h=d+p.length)}return l+i.slice(h)}]})},function(t,n,r){"use strict";var f=r(2),a=r(192),s=r(128);r(122)("search",1,function(e,i,u,c){return[function(t){var n=e(this),r=null==t?void 0:t[i];return void 0!==r?r.call(t,n):new RegExp(t)[i](String(n))},function(t){var n=c(u,t,this);if(n.done)return n.value;var r=f(t),e=String(this),i=r.lastIndex;a(i,0)||(r.lastIndex=0);var o=s(r,e);return a(r.lastIndex,i)||(r.lastIndex=i),null===o?-1:o.index}]})},function(t,n,r){"use strict";var l=r(124),x=r(2),m=r(119),S=r(136),w=r(8),_=r(128),h=r(152),e=r(4),O=Math.min,v=[].push,u="split",p="length",d="lastIndex",E=4294967295,M=!e(function(){RegExp(E,"y")});r(122)("split",2,function(i,o,y,g){var b;return b="c"=="abbc"[u](/(b)*/)[1]||4!="test"[u](/(?:)/,-1)[p]||2!="ab"[u](/(?:ab)*/)[p]||4!="."[u](/(.?)(.?)/)[p]||1<"."[u](/()()/)[p]||""[u](/.?/)[p]?function(t,n){var r=String(this);if(void 0===t&&0===n)return[];if(!l(t))return y.call(r,t,n);for(var e,i,o,u=[],c=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),f=0,a=void 0===n?E:n>>>0,s=new RegExp(t.source,c+"g");(e=h.call(s,r))&&!(f<(i=s[d])&&(u.push(r.slice(f,e.index)),1<e[p]&&e.index<r[p]&&v.apply(u,e.slice(1)),o=e[0][p],f=i,u[p]>=a));)s[d]===e.index&&s[d]++;return f===r[p]?!o&&s.test("")||u.push(""):u.push(r.slice(f)),u[p]>a?u.slice(0,a):u}:"0"[u](void 0,0)[p]?function(t,n){return void 0===t&&0===n?[]:y.call(this,t,n)}:y,[function(t,n){var r=i(this),e=null==t?void 0:t[o];return void 0!==e?e.call(t,r,n):b.call(String(r),t,n)},function(t,n){var r=g(b,t,this,n,b!==y);if(r.done)return r.value;var e=x(t),i=String(this),o=m(e,RegExp),u=e.unicode,c=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.unicode?"u":"")+(M?"y":"g"),f=new o(M?e:"^(?:"+e.source+")",c),a=void 0===n?E:n>>>0;if(0===a)return[];if(0===i.length)return null===_(f,i)?[i]:[];for(var s=0,l=0,h=[];l<i.length;){f.lastIndex=M?l:0;var v,p=_(f,M?i:i.slice(l));if(null===p||(v=O(w(f.lastIndex+(M?0:l)),i.length))===s)l=S(i,l,u);else{if(h.push(i.slice(s,l)),h.length===a)return h;for(var d=1;d<=p.length-1;d++)if(h.push(p[d]),h.length===a)return h;l=s=v}}return h.push(i.slice(s)),h}]})},function(t,n,r){"use strict";r(198);var e=r(2),i=r(115),o=r(10),u="toString",c=/./[u],f=function(t){r(27)(RegExp.prototype,u,t,!0)};r(4)(function(){return"/a/b"!=c.call({source:"a",flags:"b"})})?f(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!o&&t instanceof RegExp?i.call(t):void 0)}):c.name!=u&&f(function(){return c.call(this)})},function(t,n,r){"use strict";r(28)("anchor",function(n){return function(t){return n(this,"a","name",t)}})},function(t,n,r){"use strict";r(28)("big",function(t){return function(){return t(this,"big","","")}})},function(t,n,r){"use strict";r(28)("blink",function(t){return function(){return t(this,"blink","","")}})},function(t,n,r){"use strict";r(28)("bold",function(t){return function(){return t(this,"b","","")}})},function(t,n,r){"use strict";var e=r(1),i=r(131)(!1);e(e.P,"String",{codePointAt:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(1),u=r(8),c=r(155),f="endsWith",a=""[f];e(e.P+e.F*r(142)(f),"String",{endsWith:function(t){var n=c(this,t,f),r=1<arguments.length?arguments[1]:void 0,e=u(n.length),i=void 0===r?e:Math.min(u(r),e),o=String(t);return a?a.call(n,o,i):n.slice(i-o.length,i)===o}})},function(t,n,r){"use strict";r(28)("fixed",function(t){return function(){return t(this,"tt","","")}})},function(t,n,r){"use strict";r(28)("fontcolor",function(n){return function(t){return n(this,"font","color",t)}})},function(t,n,r){"use strict";r(28)("fontsize",function(n){return function(t){return n(this,"font","size",t)}})},function(t,n,r){var e=r(1),o=r(78),u=String.fromCharCode,i=String.fromCodePoint;e(e.S+e.F*(!!i&&1!=i.length),"String",{fromCodePoint:function(t){for(var n,r=[],e=arguments.length,i=0;i<e;){if(n=+arguments[i++],o(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?u(n):u(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},function(t,n,r){"use strict";var e=r(1),i=r(155);e(e.P+e.F*r(142)("includes"),"String",{includes:function(t){return!!~i(this,t,"includes").indexOf(t,1<arguments.length?arguments[1]:void 0)}})},function(t,n,r){"use strict";r(28)("italics",function(t){return function(){return t(this,"i","","")}})},function(t,n,r){"use strict";var e=r(131)(!0);r(147)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";r(28)("link",function(n){return function(t){return n(this,"a","href",t)}})},function(t,n,r){var e=r(1),u=r(33),c=r(8);e(e.S,"String",{raw:function(t){for(var n=u(t.raw),r=c(n.length),e=arguments.length,i=[],o=0;o<r;)i.push(String(n[o++])),o<e&&i.push(String(arguments[o]));return i.join("")}})},function(t,n,r){var e=r(1);e(e.P,"String",{repeat:r(156)})},function(t,n,r){"use strict";r(28)("small",function(t){return function(){return t(this,"small","","")}})},function(t,n,r){"use strict";var e=r(1),i=r(8),o=r(155),u="startsWith",c=""[u];e(e.P+e.F*r(142)(u),"String",{startsWith:function(t){var n=o(this,t,u),r=i(Math.min(1<arguments.length?arguments[1]:void 0,n.length)),e=String(t);return c?c.call(n,e,r):n.slice(r,r+e.length)===e}})},function(t,n,r){"use strict";r(28)("strike",function(t){return function(){return t(this,"strike","","")}})},function(t,n,r){"use strict";r(28)("sub",function(t){return function(){return t(this,"sub","","")}})},function(t,n,r){"use strict";r(28)("sup",function(t){return function(){return t(this,"sup","","")}})},function(t,n,r){"use strict";r(84)("trim",function(t){return function(){return t(this,3)}})},function(t,n,r){"use strict";var e=r(3),u=r(30),i=r(10),o=r(1),c=r(27),f=r(69).KEY,a=r(4),s=r(118),l=r(83),h=r(79),v=r(7),p=r(195),d=r(160),y=r(218),g=r(123),b=r(2),x=r(5),m=r(17),S=r(33),w=r(53),_=r(75),O=r(72),E=r(184),M=r(31),P=r(127),j=r(11),F=r(74),A=M.f,I=j.f,L=E.f,N=e.Symbol,T=e.JSON,k=T&&T.stringify,R="prototype",C=v("_hidden"),D=v("toPrimitive"),G={}.propertyIsEnumerable,W=s("symbol-registry"),U=s("symbols"),V=s("op-symbols"),B=Object[R],q="function"==typeof N&&!!P.f,z=e.QObject,K=!z||!z[R]||!z[R].findChild,H=i&&a(function(){return 7!=O(I({},"a",{get:function(){return I(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=A(B,n);e&&delete B[n],I(t,n,r),e&&t!==B&&I(B,n,e)}:I,J=function(t){var n=U[t]=O(N[R]);return n._k=t,n},$=q&&"symbol"==typeof N.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof N},Y=function(t,n,r){return t===B&&Y(V,n,r),b(t),n=w(n,!0),b(r),u(U,n)?(r.enumerable?(u(t,C)&&t[C][n]&&(t[C][n]=!1),r=O(r,{enumerable:_(0,!1)})):(u(t,C)||I(t,C,_(1,{})),t[C][n]=!0),H(t,n,r)):I(t,n,r)},X=function(t,n){b(t);for(var r,e=y(n=S(n)),i=0,o=e.length;i<o;)Y(t,r=e[i++],n[r]);return t},Q=function(t){var n=G.call(this,t=w(t,!0));return!(this===B&&u(U,t)&&!u(V,t))&&(!(n||!u(this,t)||!u(U,t)||u(this,C)&&this[C][t])||n)},Z=function(t,n){if(t=S(t),n=w(n,!0),t!==B||!u(U,n)||u(V,n)){var r=A(t,n);return!r||!u(U,n)||u(t,C)&&t[C][n]||(r.enumerable=!0),r}},tt=function(t){for(var n,r=L(S(t)),e=[],i=0;r.length>i;)u(U,n=r[i++])||n==C||n==f||e.push(n);return e},nt=function(t){for(var n,r=t===B,e=L(r?V:S(t)),i=[],o=0;e.length>o;)!u(U,n=e[o++])||r&&!u(B,n)||i.push(U[n]);return i};q||(c((N=function(){if(this instanceof N)throw TypeError("Symbol is not a constructor!");var n=h(0<arguments.length?arguments[0]:void 0),r=function(t){this===B&&r.call(V,t),u(this,C)&&u(this[C],n)&&(this[C][n]=!1),H(this,n,_(1,t))};return i&&K&&H(B,n,{configurable:!0,set:r}),J(n)})[R],"toString",function(){return this._k}),M.f=Z,j.f=Y,r(73).f=E.f=tt,r(117).f=Q,P.f=nt,i&&!r(68)&&c(B,"propertyIsEnumerable",Q,!0),p.f=function(t){return J(v(t))}),o(o.G+o.W+o.F*!q,{Symbol:N});for(var rt="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),et=0;rt.length>et;)v(rt[et++]);for(var it=F(v.store),ot=0;it.length>ot;)d(it[ot++]);o(o.S+o.F*!q,"Symbol",{for:function(t){return u(W,t+="")?W[t]:W[t]=N(t)},keyFor:function(t){if(!$(t))throw TypeError(t+" is not a symbol!");for(var n in W)if(W[n]===t)return n},useSetter:function(){K=!0},useSimple:function(){K=!1}}),o(o.S+o.F*!q,"Object",{create:function(t,n){return void 0===n?O(t):X(O(t),n)},defineProperty:Y,defineProperties:X,getOwnPropertyDescriptor:Z,getOwnPropertyNames:tt,getOwnPropertySymbols:nt});var ut=a(function(){P.f(1)});o(o.S+o.F*ut,"Object",{getOwnPropertySymbols:function(t){return P.f(m(t))}}),T&&o(o.S+o.F*(!q||a(function(){var t=N();return"[null]"!=k([t])||"{}"!=k({a:t})||"{}"!=k(Object(t))})),"JSON",{stringify:function(t){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);if(r=n=e[1],(x(n)||void 0!==t)&&!$(t))return g(n)||(n=function(t,n){if("function"==typeof r&&(n=r.call(this,t,n)),!$(n))return n}),e[1]=n,k.apply(T,e)}}),N[R][D]||r(26)(N[R],D,N[R].valueOf),l(N,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){"use strict";var e=r(1),i=r(132),o=r(159),a=r(2),s=r(78),l=r(8),u=r(5),c=r(3).ArrayBuffer,h=r(119),v=o.ArrayBuffer,p=o.DataView,f=i.ABV&&c.isView,d=v.prototype.slice,y=i.VIEW,g="ArrayBuffer";e(e.G+e.W+e.F*(c!==v),{ArrayBuffer:v}),e(e.S+e.F*!i.CONSTR,g,{isView:function(t){return f&&f(t)||u(t)&&y in t}}),e(e.P+e.U+e.F*r(4)(function(){return!new v(2).slice(1,void 0).byteLength}),g,{slice:function(t,n){if(void 0!==d&&void 0===n)return d.call(a(this),t);for(var r=a(this).byteLength,e=s(t,r),i=s(void 0===n?r:n,r),o=new(h(this,v))(l(i-e)),u=new p(this),c=new p(o),f=0;e<i;)c.setUint8(f++,u.getUint8(e++));return o}}),r(77)(g)},function(t,n,r){var e=r(1);e(e.G+e.W+e.F*!r(132).ABV,{DataView:r(159).DataView})},function(t,n,r){r(57)("Float32",4,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(t,n,r){r(57)("Float64",8,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(t,n,r){r(57)("Int16",2,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(t,n,r){r(57)("Int32",4,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(t,n,r){r(57)("Int8",1,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(t,n,r){r(57)("Uint16",2,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(t,n,r){r(57)("Uint32",4,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(t,n,r){r(57)("Uint8",1,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(t,n,r){r(57)("Uint8",1,function(e){return function(t,n,r){return e(this,t,n,r)}},!0)},function(t,n,r){"use strict";var e=r(172),i=r(80);r(121)("WeakSet",function(t){return function(){return t(this,0<arguments.length?arguments[0]:void 0)}},{add:function(t){return e.def(i(this,"WeakSet"),t,!0)}},e,!1,!0)},function(t,n,r){"use strict";var e=r(1),i=r(173),o=r(17),u=r(8),c=r(21),f=r(138);e(e.P,"Array",{flatMap:function(t){var n,r,e=o(this);return c(t),n=u(e.length),r=f(e,0),i(r,e,e,n,0,1,t,arguments[1]),r}}),r(67)("flatMap")},function(t,n,r){"use strict";var e=r(1),i=r(173),o=r(17),u=r(8),c=r(49),f=r(138);e(e.P,"Array",{flatten:function(){var t=arguments[0],n=o(this),r=u(n.length),e=f(n,0);return i(e,n,n,r,0,void 0===t?1:c(t)),e}}),r(67)("flatten")},function(t,n,r){"use strict";var e=r(1),i=r(120)(!0);e(e.P,"Array",{includes:function(t){return i(this,t,1<arguments.length?arguments[1]:void 0)}}),r(67)("includes")},function(t,n,r){var e=r(1),i=r(150)(),o=r(3).process,u="process"==r(45)(o);e(e.G,{asap:function(t){var n=u&&o.domain;i(n?n.bind(t):t)}})},function(t,n,r){var e=r(1),i=r(45);e(e.S,"Error",{isError:function(t){return"Error"===i(t)}})},function(t,n,r){var e=r(1);e(e.G,{global:r(3)})},function(t,n,r){r(129)("Map")},function(t,n,r){r(130)("Map")},function(t,n,r){var e=r(1);e(e.P+e.R,"Map",{toJSON:r(171)("Map")})},function(t,n,r){var e=r(1);e(e.S,"Math",{clamp:function(t,n,r){return Math.min(r,Math.max(n,t))}})},function(t,n,r){var e=r(1);e(e.S,"Math",{DEG_PER_RAD:Math.PI/180})},function(t,n,r){var e=r(1),i=180/Math.PI;e(e.S,"Math",{degrees:function(t){return t*i}})},function(t,n,r){var e=r(1),o=r(181),u=r(179);e(e.S,"Math",{fscale:function(t,n,r,e,i){return u(o(t,n,r,e,i))}})},function(t,n,r){var e=r(1);e(e.S,"Math",{iaddh:function(t,n,r,e){var i=t>>>0,o=r>>>0;return(n>>>0)+(e>>>0)+((i&o|(i|o)&~(i+o>>>0))>>>31)|0}})},function(t,n,r){var e=r(1);e(e.S,"Math",{imulh:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e,u=r>>16,c=e>>16,f=(u*o>>>0)+(i*o>>>16);return u*c+(f>>16)+((i*c>>>0)+(65535&f)>>16)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{isubh:function(t,n,r,e){var i=t>>>0,o=r>>>0;return(n>>>0)-(e>>>0)-((~i&o|~(i^o)&i-o>>>0)>>>31)|0}})},function(t,n,r){var e=r(1);e(e.S,"Math",{RAD_PER_DEG:180/Math.PI})},function(t,n,r){var e=r(1),i=Math.PI/180;e(e.S,"Math",{radians:function(t){return t*i}})},function(t,n,r){var e=r(1);e(e.S,"Math",{scale:r(181)})},function(t,n,r){var e=r(1);e(e.S,"Math",{signbit:function(t){return(t=+t)!=t?t:0==t?1/t==1/0:0<t}})},function(t,n,r){var e=r(1);e(e.S,"Math",{umulh:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e,u=r>>>16,c=e>>>16,f=(u*o>>>0)+(i*o>>>16);return u*c+(f>>>16)+((i*c>>>0)+(65535&f)>>>16)}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(21),u=r(11);r(10)&&e(e.P+r(126),"Object",{__defineGetter__:function(t,n){u.f(i(this),t,{get:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(21),u=r(11);r(10)&&e(e.P+r(126),"Object",{__defineSetter__:function(t,n){u.f(i(this),t,{set:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){var e=r(1),i=r(186)(!0);e(e.S,"Object",{entries:function(t){return i(t)}})},function(t,n,r){var e=r(1),f=r(187),a=r(33),s=r(31),l=r(139);e(e.S,"Object",{getOwnPropertyDescriptors:function(t){for(var n,r,e=a(t),i=s.f,o=f(e),u={},c=0;o.length>c;)void 0!==(r=i(e,n=o[c++]))&&l(u,n,r);return u}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(53),u=r(32),c=r(31).f;r(10)&&e(e.P+r(126),"Object",{__lookupGetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.get}while(r=u(r))}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(53),u=r(32),c=r(31).f;r(10)&&e(e.P+r(126),"Object",{__lookupSetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.set}while(r=u(r))}})},function(t,n,r){var e=r(1),i=r(186)(!1);e(e.S,"Object",{values:function(t){return i(t)}})},function(t,n,r){"use strict";var e=r(1),o=r(3),u=r(46),i=r(150)(),c=r(7)("observable"),f=r(21),a=r(2),s=r(70),l=r(76),h=r(26),v=r(71),p=v.RETURN,d=function(t){return null==t?void 0:f(t)},y=function(t){var n=t._c;n&&(t._c=void 0,n())},g=function(t){return void 0===t._o},b=function(t){g(t)||(t._o=void 0,y(t))},x=function(t,n){a(t),this._c=void 0,this._o=t,t=new m(this);try{var r=n(t),e=r;null!=r&&("function"==typeof r.unsubscribe?r=function(){e.unsubscribe()}:f(r),this._c=r)}catch(n){return void t.error(n)}g(this)&&y(this)};x.prototype=l({},{unsubscribe:function(){b(this)}});var m=function(t){this._s=t};m.prototype=l({},{next:function(t){var n=this._s;if(!g(n)){var r=n._o;try{var e=d(r.next);if(e)return e.call(r,t)}catch(t){try{b(n)}finally{throw t}}}},error:function(t){var n=this._s;if(g(n))throw t;var r=n._o;n._o=void 0;try{var e=d(r.error);if(!e)throw t;t=e.call(r,t)}catch(t){try{y(n)}finally{throw t}}return y(n),t},complete:function(t){var n=this._s;if(!g(n)){var r=n._o;n._o=void 0;try{var e=d(r.complete);t=e?e.call(r,t):void 0}catch(t){try{y(n)}finally{throw t}}return y(n),t}}});var S=function(t){s(this,S,"Observable","_f")._f=f(t)};l(S.prototype,{subscribe:function(t){return new x(t,this._f)},forEach:function(e){var i=this;return new(u.Promise||o.Promise)(function(t,n){f(e);var r=i.subscribe({next:function(t){try{return e(t)}catch(t){n(t),r.unsubscribe()}},error:n,complete:t})})}}),l(S,{from:function(t){var n="function"==typeof this?this:S,r=d(a(t)[c]);if(r){var e=a(r.call(t));return e.constructor===n?e:new n(function(t){return e.subscribe(t)})}return new n(function(n){var r=!1;return i(function(){if(!r){try{if(v(t,!1,function(t){if(n.next(t),r)return p})===p)return}catch(t){if(r)throw t;return void n.error(t)}n.complete()}}),function(){r=!0}})},of:function(){for(var t=0,n=arguments.length,e=new Array(n);t<n;)e[t]=arguments[t++];return new("function"==typeof this?this:S)(function(n){var r=!1;return i(function(){if(!r){for(var t=0;t<e.length;++t)if(n.next(e[t]),r)return;n.complete()}}),function(){r=!0}})}}),h(S.prototype,c,function(){return this}),e(e.G,{Observable:S}),r(77)("Observable")},function(t,n,r){"use strict";var e=r(1),i=r(46),o=r(3),u=r(119),c=r(191);e(e.P+e.R,"Promise",{finally:function(n){var r=u(this,i.Promise||o.Promise),t="function"==typeof n;return this.then(t?function(t){return c(r,n()).then(function(){return t})}:n,t?function(t){return c(r,n()).then(function(){throw t})}:n)}})},function(t,n,r){"use strict";var e=r(1),i=r(151),o=r(190);e(e.S,"Promise",{try:function(t){var n=i.f(this),r=o(t);return(r.e?n.reject:n.resolve)(r.v),n.promise}})},function(t,n,r){var e=r(56),i=r(2),o=e.key,u=e.set;e.exp({defineMetadata:function(t,n,r,e){u(t,n,i(r),o(e))}})},function(t,n,r){var e=r(56),o=r(2),u=e.key,c=e.map,f=e.store;e.exp({deleteMetadata:function(t,n){var r=arguments.length<3?void 0:u(arguments[2]),e=c(o(n),r,!1);if(void 0===e||!e.delete(t))return!1;if(e.size)return!0;var i=f.get(n);return i.delete(r),!!i.size||f.delete(n)}})},function(t,n,r){var o=r(199),u=r(167),e=r(56),i=r(2),c=r(32),f=e.keys,a=e.key,s=function(t,n){var r=f(t,n),e=c(t);if(null===e)return r;var i=s(e,n);return i.length?r.length?u(new o(r.concat(i))):i:r};e.exp({getMetadataKeys:function(t){return s(i(t),arguments.length<2?void 0:a(arguments[1]))}})},function(t,n,r){var e=r(56),i=r(2),o=r(32),u=e.has,c=e.get,f=e.key,a=function(t,n,r){if(u(t,n,r))return c(t,n,r);var e=o(n);return null!==e?a(t,e,r):void 0};e.exp({getMetadata:function(t,n){return a(t,i(n),arguments.length<3?void 0:f(arguments[2]))}})},function(t,n,r){var e=r(56),i=r(2),o=e.keys,u=e.key;e.exp({getOwnMetadataKeys:function(t){return o(i(t),arguments.length<2?void 0:u(arguments[1]))}})},function(t,n,r){var e=r(56),i=r(2),o=e.get,u=e.key;e.exp({getOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(56),i=r(2),o=r(32),u=e.has,c=e.key,f=function(t,n,r){if(u(t,n,r))return!0;var e=o(n);return null!==e&&f(t,e,r)};e.exp({hasMetadata:function(t,n){return f(t,i(n),arguments.length<3?void 0:c(arguments[2]))}})},function(t,n,r){var e=r(56),i=r(2),o=e.has,u=e.key;e.exp({hasOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(56),i=r(2),o=r(21),u=e.key,c=e.set;e.exp({metadata:function(r,e){return function(t,n){c(r,e,(void 0!==n?i:o)(t),u(n))}}})},function(t,n,r){r(129)("Set")},function(t,n,r){r(130)("Set")},function(t,n,r){var e=r(1);e(e.P+e.R,"Set",{toJSON:r(171)("Set")})},function(t,n,r){"use strict";var e=r(1),i=r(131)(!0);e(e.P,"String",{at:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(1),i=r(51),o=r(8),u=r(124),c=r(115),f=RegExp.prototype,a=function(t,n){this._r=t,this._s=n};r(146)(a,"RegExp String",function(){var t=this._r.exec(this._s);return{value:t,done:null===t}}),e(e.P,"String",{matchAll:function(t){if(i(this),!u(t))throw TypeError(t+" is not a regexp!");var n=String(this),r="flags"in f?String(t.flags):c.call(t),e=new RegExp(t.source,~r.indexOf("g")?r:"g"+r);return e.lastIndex=o(t.lastIndex),new a(e,n)}})},function(t,n,r){"use strict";var e=r(1),i=r(193),o=r(133),u=/Version\/10\.\d+(\.\d+)?( Mobile\/\w+)? Safari\//.test(o);e(e.P+e.F*u,"String",{padEnd:function(t){return i(this,t,1<arguments.length?arguments[1]:void 0,!1)}})},function(t,n,r){"use strict";var e=r(1),i=r(193),o=r(133),u=/Version\/10\.\d+(\.\d+)?( Mobile\/\w+)? Safari\//.test(o);e(e.P+e.F*u,"String",{padStart:function(t){return i(this,t,1<arguments.length?arguments[1]:void 0,!0)}})},function(t,n,r){"use strict";r(84)("trimLeft",function(t){return function(){return t(this,1)}},"trimStart")},function(t,n,r){"use strict";r(84)("trimRight",function(t){return function(){return t(this,2)}},"trimEnd")},function(t,n,r){r(160)("asyncIterator")},function(t,n,r){r(160)("observable")},function(t,n,r){var e=r(1);e(e.S,"System",{global:r(3)})},function(t,n,r){r(129)("WeakMap")},function(t,n,r){r(130)("WeakMap")},function(t,n,r){r(129)("WeakSet")},function(t,n,r){r(130)("WeakSet")},function(t,n,r){for(var e=r(162),i=r(74),o=r(27),u=r(3),c=r(26),f=r(82),a=r(7),s=a("iterator"),l=a("toStringTag"),h=f.Array,v={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},p=i(v),d=0;d<p.length;d++){var y,g=p[d],b=v[g],x=u[g],m=x&&x.prototype;if(m&&(m[s]||c(m,s,h),m[l]||c(m,l,g),f[g]=h,b))for(y in e)m[y]||o(m,y,e[y],!0)}},function(t,n,r){var e=r(1),i=r(158);e(e.G+e.B,{setImmediate:i.set,clearImmediate:i.clear})},function(t,n,r){var e=r(3),i=r(1),o=r(133),u=[].slice,c=/MSIE .\./.test(o),f=function(i){return function(t,n){var r=2<arguments.length,e=!!r&&u.call(arguments,2);return i(r?function(){("function"==typeof t?t:Function(t)).apply(this,e)}:t,n)}};i(i.G+i.B+i.F*c,{setTimeout:f(e.setTimeout),setInterval:f(e.setInterval)})},function(t,n,r){r(341),r(280),r(282),r(281),r(284),r(286),r(291),r(285),r(283),r(293),r(292),r(288),r(289),r(287),r(279),r(290),r(294),r(295),r(247),r(249),r(248),r(297),r(296),r(267),r(277),r(278),r(268),r(269),r(270),r(271),r(272),r(273),r(274),r(275),r(276),r(250),r(251),r(252),r(253),r(254),r(255),r(256),r(257),r(258),r(259),r(260),r(261),r(262),r(263),r(264),r(265),r(266),r(328),r(333),r(340),r(331),r(323),r(324),r(329),r(334),r(336),r(319),r(320),r(321),r(322),r(325),r(326),r(327),r(330),r(332),r(335),r(337),r(338),r(339),r(242),r(244),r(243),r(246),r(245),r(231),r(229),r(235),r(232),r(238),r(240),r(228),r(234),r(225),r(239),r(223),r(237),r(236),r(230),r(233),r(222),r(224),r(227),r(226),r(241),r(162),r(313),r(197),r(318),r(198),r(314),r(315),r(316),r(317),r(298),r(196),r(199),r(200),r(353),r(342),r(343),r(348),r(351),r(352),r(346),r(349),r(347),r(350),r(344),r(345),r(299),r(300),r(301),r(302),r(303),r(306),r(304),r(305),r(307),r(308),r(309),r(310),r(312),r(311),r(356),r(354),r(355),r(397),r(400),r(399),r(401),r(402),r(398),r(403),r(404),r(378),r(381),r(377),r(375),r(376),r(379),r(380),r(362),r(396),r(361),r(395),r(407),r(409),r(360),r(394),r(406),r(408),r(359),r(405),r(358),r(363),r(364),r(365),r(366),r(367),r(369),r(368),r(370),r(371),r(372),r(374),r(373),r(383),r(384),r(385),r(386),r(388),r(387),r(390),r(389),r(391),r(392),r(393),r(357),r(382),r(412),r(411),r(410),t.exports=r(46)},function(t,n){t.exports=function(t,n){if("string"==typeof n)return t.insertAdjacentHTML("afterend",n);var r=t.nextSibling;return r?t.parentNode.insertBefore(n,r):t.parentNode.appendChild(n)}}])</script><script src="/myblog/./main.68f21c.js"></script><script>!function(){var e,t;e="/myblog/slider.27463f.js",t=document.createElement("script"),document.getElementsByTagName("body")[0].appendChild(t),t.setAttribute("src",e)}()</script>



<!--  -->

<script>
  /* 标签页标题切换 */
  var originTitle = document.title;
  var titleTime;
  document.addEventListener("visibilitychange", function () {
    if (document.hidden) {
      document.title = "(つェ⊂) 我藏好了哦~ " + originTitle;
      clearTimeout(titleTime);
    } else {
      document.title = "(*´∇｀*) 被你发现啦~ " + originTitle;
      titleTime = setTimeout(function () {
        document.title = originTitle;
      }, 2000);
    }
  });
</script>


    
<div class="tools-col" q-class="show:isShow,hide:isShow|isFalse" q-on="click:stop(e)">
  <div class="tools-nav header-menu">
    
    
      
      
      
    
      
      
      
    
      
      
      
    
    

    <ul style="width: 70%">
    
    
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'innerArchive')"><a href="javascript:void(0)" q-class="active:innerArchive">所有文章</a></li>
      
        
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'friends')"><a href="javascript:void(0)" q-class="active:friends">友链</a></li>
      
        
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'aboutme')"><a href="javascript:void(0)" q-class="active:aboutme">关于我</a></li>
      
        
    </ul>
  </div>
  <div class="tools-wrap">
    
    	<section class="tools-section tools-section-all" q-show="innerArchive">
        <div class="search-wrap">
          <input class="search-ipt" q-model="search" type="text" placeholder="find something…">
          <i class="icon-search icon" q-show="search|isEmptyStr"></i>
          <i class="icon-close icon" q-show="search|isNotEmptyStr" q-on="click:clearChose(e)"></i>
        </div>
        <div class="widget tagcloud search-tag">
          <p class="search-tag-wording">tag:</p>
          <label class="search-switch">
            <input type="checkbox" q-on="click:toggleTag(e)" q-attr="checked:showTags">
          </label>
          <ul class="article-tag-list" q-show="showTags">
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color5">Java</a>
              </li>
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color1">Linux</a>
              </li>
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color4">数据库</a>
              </li>
            
            <div class="clearfix"></div>
          </ul>
        </div>
        <ul class="search-ul">
          <p q-show="jsonFail" style="padding: 20px; font-size: 12px;">
            缺失模块。<br/>1、请确保node版本大于6.2<br/>2、在博客根目录（注意不是yilia-plus根目录）执行以下命令：<br/> npm i hexo-generator-json-content --save<br/><br/>
            3、在根目录_config.yml里添加配置：
<pre style="font-size: 12px;" q-show="jsonFail">
  jsonContent:
    meta: false
    pages: false
    posts:
      title: true
      date: true
      path: true
      text: false
      raw: false
      content: false
      slug: false
      updated: false
      comments: false
      link: false
      permalink: false
      excerpt: false
      categories: false
      tags: true
</pre>
          </p>
          <li class="search-li" q-repeat="items" q-show="isShow">
            <a q-attr="href:path|urlformat" class="search-title"><i class="icon-quo-left icon"></i><span q-text="title"></span></a>
            <p class="search-time">
              <i class="icon-calendar icon"></i>
              <span q-text="date|dateformat"></span>
            </p>
            <p class="search-tag">
              <i class="icon-price-tags icon"></i>
              <span q-repeat="tags" q-on="click:choseTag(e, name)" q-text="name|tagformat"></span>
            </p>
          </li>
        </ul>
    	</section>
    

    
    	<section class="tools-section tools-section-friends" q-show="friends">
  		
        <ul class="search-ul">
          
            <li class="search-li">
              <a href="https://gitee.com/AngeGit" target="_blank" class="search-title" title="记录工作和学习过程中的笔记：Java、前端开发、Hexo博客、聚合支付、Linux笔记、ElasticSearch、ELK日志分析">
                
                  <img src="http://angegit.gitee.io/cv/head.jpg">
                
                技术笔记
              </a>
            </li>
          
            <li class="search-li">
              <a href="https://gitee.com/AngeGit" target="_blank" class="search-title" >
                
                  <i class="icon-link icon"></i>
                
                GitHub
              </a>
            </li>
          
            <li class="search-li">
              <a href="https://gitee.com/AngeGit" target="_blank" class="search-title" >
                
                  <i class="icon-link icon"></i>
                
                码云
              </a>
            </li>
          
            <li class="search-li">
              <a href="https://gitee.com/AngeGit" target="_blank" class="search-title" >
                
                  <i class="icon-link icon"></i>
                
                简书
              </a>
            </li>
          
            <li class="search-li">
              <a href="https://gitee.com/AngeGit" target="_blank" class="search-title" >
                
                  <i class="icon-link icon"></i>
                
                CSDN
              </a>
            </li>
          
        </ul>
  		
    	</section>
    

    
    	<section class="tools-section tools-section-me" q-show="aboutme">
        
          
  	  		<div class="aboutme-wrap" id="aboutme">主要涉及技术：<br>Java后端开发、聚合支付、<br>公众号开发、开源爱好者、Linux<br><br>联系QQ:1351261434<br><br>很惭愧<br><br>只做了一点微小的工作<br>谢谢大家</div>
  	  	
    	</section>
    
  </div>
  
</div>
    <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                      <div class="pswp__preloader__cut">
                        <div class="pswp__preloader__donut"></div>
                      </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div> 
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>
  </div>

  
  
<script type="text/javascript" src="/myblog/plugins/activate-power-mode/activate-power-mode.js"></script>
<script>
  POWERMODE.colorful = true; // make power mode colorful
  POWERMODE.shake = false; // turn off shake
  document.body.addEventListener('input', POWERMODE);
</script>

  
  <!-- <script async type="text/javascript" size="90" alpha="0.2" zIndex="0" src="/myblog/plugins/ribbon.js/ribbon.min.js"></script> -->
  
  
  
  <script type="text/javascript" src="/myblog/lib/snow.js"></script>
  <script type="text/javascript" src="/myblog/lib/jquery-2.1.4.min.js"></script>
  <script>
    snow.down();
    $(window).resize(function() {
      $("canvas").css("z-index","500").remove();
      snow.down();
    });
  </script>
  
</body>

</html>
